diff --git a/.gitignore b/.gitignore
index 7965f6a404368e..b0243685138bac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,6 +71,8 @@ Lib/test/data/*
/Makefile
/Makefile.pre
/iOSTestbed.*
+/tvOSTestbed.*
+/visionOSTestbed.*
iOS/Frameworks/
iOS/Resources/Info.plist
iOS/testbed/build
@@ -81,6 +83,27 @@ iOS/testbed/Python.xcframework/ios-*/Python.framework
iOS/testbed/iOSTestbed.xcodeproj/project.xcworkspace
iOS/testbed/iOSTestbed.xcodeproj/xcuserdata
iOS/testbed/iOSTestbed.xcodeproj/xcshareddata
+tvOS/testbed/build
+tvOS/testbed/Python.xcframework/tvos-*/bin
+tvOS/testbed/Python.xcframework/tvos-*/include
+tvOS/testbed/Python.xcframework/tvos-*/lib
+tvOS/testbed/Python.xcframework/tvos-*/Python.framework
+tvOS/testbed/tvOSTestbed.xcodeproj/project.xcworkspace
+tvOS/testbed/tvOSTestbed.xcodeproj/xcuserdata
+tvOS/testbed/tvOSTestbed.xcodeproj/xcshareddata
+visionOS/testbed/Python.xcframework/xros-*/bin
+visionOS/testbed/Python.xcframework/xros-*/include
+visionOS/testbed/Python.xcframework/xros-*/lib
+visionOS/testbed/Python.xcframework/xros-*/Python.framework
+visionOS/testbed/visionOSTestbed.xcodeproj/project.xcworkspace
+visionOS/testbed/visionOSTestbed.xcodeproj/xcuserdata
+visionOS/testbed/visionOSTestbed.xcodeproj/xcshareddata
+tvOS/Frameworks
+tvOS/Resources/Info.plist
+watchOS/Frameworks
+watchOS/Resources/Info.plist
+visionOS/Frameworks
+visionOS/Resources/Info.plist
Mac/Makefile
Mac/PythonLauncher/Info.plist
Mac/PythonLauncher/Makefile
diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py
index 823a3692fd1bbf..00639dd8488267 100644
--- a/Lib/ctypes/__init__.py
+++ b/Lib/ctypes/__init__.py
@@ -419,7 +419,7 @@ def __init__(self, name, mode=DEFAULT_MODE, handle=None,
if name:
name = _os.fspath(name)
- # If the filename that has been provided is an iOS/tvOS/watchOS
+ # If the filename that has been provided is an iOS/tvOS/watchOS/visionOS
# .fwork file, dereference the location to the true origin of the
# binary.
if name.endswith(".fwork"):
diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
index 99504911a3dbe0..527c2f36dd07f2 100644
--- a/Lib/ctypes/util.py
+++ b/Lib/ctypes/util.py
@@ -126,7 +126,7 @@ def dllist():
if (name := _get_module_filename(h)) is not None]
return libraries
-elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}:
+elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos", "visionos"}:
from ctypes.macholib.dyld import dyld_find as _dyld_find
def find_library(name):
possible = ['lib%s.dylib' % name,
@@ -425,7 +425,7 @@ def find_library(name):
# https://man.openbsd.org/dl_iterate_phdr
# https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html
if (os.name == "posix" and
- sys.platform not in {"darwin", "ios", "tvos", "watchos"}):
+ sys.platform not in {"darwin", "ios", "tvos", "watchos", "visionos"}):
import ctypes
if hasattr((_libc := ctypes.CDLL(None)), "dl_iterate_phdr"):
diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py
index 8bcd741c446bd2..d8a6f28edba39c 100644
--- a/Lib/importlib/_bootstrap_external.py
+++ b/Lib/importlib/_bootstrap_external.py
@@ -52,7 +52,7 @@
# Bootstrap-related code ######################################################
_CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win',
-_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos'
+_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos', 'visionos'
_CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY
+ _CASE_INSENSITIVE_PLATFORMS_STR_KEY)
@@ -1535,7 +1535,7 @@ def _get_supported_file_loaders():
"""
extension_loaders = []
if hasattr(_imp, 'create_dynamic'):
- if sys.platform in {"ios", "tvos", "watchos"}:
+ if sys.platform in {"ios", "tvos", "watchos", "visionos"}:
extension_loaders = [(AppleFrameworkLoader, [
suffix.replace(".so", ".fwork")
for suffix in _imp.extension_suffixes()
diff --git a/Lib/platform.py b/Lib/platform.py
index 55e211212d4209..cad919bc0c4969 100644
--- a/Lib/platform.py
+++ b/Lib/platform.py
@@ -528,6 +528,78 @@ def ios_ver(system="", release="", model="", is_simulator=False):
return IOSVersionInfo(system, release, model, is_simulator)
+# A namedtuple for tvOS version information.
+TVOSVersionInfo = collections.namedtuple(
+ "TVOSVersionInfo",
+ ["system", "release", "model", "is_simulator"]
+)
+
+
+def tvos_ver(system="", release="", model="", is_simulator=False):
+ """Get tvOS version information, and return it as a namedtuple:
+ (system, release, model, is_simulator).
+
+ If values can't be determined, they are set to values provided as
+ parameters.
+ """
+ if sys.platform == "tvos":
+ # TODO: Can the iOS implementation be used here?
+ import _ios_support
+ result = _ios_support.get_platform_ios()
+ if result is not None:
+ return TVOSVersionInfo(*result)
+
+ return TVOSVersionInfo(system, release, model, is_simulator)
+
+
+# A namedtuple for watchOS version information.
+WatchOSVersionInfo = collections.namedtuple(
+ "WatchOSVersionInfo",
+ ["system", "release", "model", "is_simulator"]
+)
+
+
+def watchos_ver(system="", release="", model="", is_simulator=False):
+ """Get watchOS version information, and return it as a namedtuple:
+ (system, release, model, is_simulator).
+
+ If values can't be determined, they are set to values provided as
+ parameters.
+ """
+ if sys.platform == "watchos":
+ # TODO: Can the iOS implementation be used here?
+ import _ios_support
+ result = _ios_support.get_platform_ios()
+ if result is not None:
+ return WatchOSVersionInfo(*result)
+
+ return WatchOSVersionInfo(system, release, model, is_simulator)
+
+
+# A namedtuple for visionOS version information.
+VisionOSVersionInfo = collections.namedtuple(
+ "VisionOSVersionInfo",
+ ["system", "release", "model", "is_simulator"]
+)
+
+
+def visionos_ver(system="", release="", model="", is_simulator=False):
+ """Get visionOS version information, and return it as a namedtuple:
+ (system, release, model, is_simulator).
+
+ If values can't be determined, they are set to values provided as
+ parameters.
+ """
+ if sys.platform == "visionos":
+ # TODO: Can the iOS implementation be used here?
+ import _ios_support
+ result = _ios_support.get_platform_ios()
+ if result is not None:
+ return VisionOSVersionInfo(*result)
+
+ return VisionOSVersionInfo(system, release, model, is_simulator)
+
+
def _java_getprop(name, default):
"""This private helper is deprecated in 3.13 and will be removed in 3.15"""
from java.lang import System
@@ -727,7 +799,7 @@ def _syscmd_file(target, default=''):
default in case the command should fail.
"""
- if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'watchos'}:
+ if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'watchos', 'visionos'}:
# XXX Others too ?
return default
@@ -891,14 +963,30 @@ def get_OpenVMS():
csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0)
return 'Alpha' if cpu_number >= 128 else 'VAX'
- # On the iOS simulator, os.uname returns the architecture as uname.machine.
- # On device it returns the model name for some reason; but there's only one
- # CPU architecture for iOS devices, so we know the right answer.
+ # On the iOS/tvOS/watchOS/visionOS simulator, os.uname returns the architecture as
+ # uname.machine. On device it returns the model name for some reason; but
+ # there's only one CPU architecture for devices, so we know the right
+ # answer.
def get_ios():
if sys.implementation._multiarch.endswith("simulator"):
return os.uname().machine
return 'arm64'
+ def get_tvos():
+ if sys.implementation._multiarch.endswith("simulator"):
+ return os.uname().machine
+ return 'arm64'
+
+ def get_watchos():
+ if sys.implementation._multiarch.endswith("simulator"):
+ return os.uname().machine
+ return 'arm64_32'
+
+ def get_visionos():
+ if sys.implementation._multiarch.endswith("simulator"):
+ return os.uname().machine
+ return 'arm64'
+
def from_subprocess():
"""
Fall back to `uname -p`
@@ -1058,9 +1146,15 @@ def uname():
system = 'Android'
release = android_ver().release
- # Normalize responses on iOS
+ # Normalize responses on Apple mobile platforms
if sys.platform == 'ios':
system, release, _, _ = ios_ver()
+ if sys.platform == 'tvos':
+ system, release, _, _ = tvos_ver()
+ if sys.platform == 'watchos':
+ system, release, _, _ = watchos_ver()
+ if sys.platform == 'visionos':
+ system, release, _, _ = visionos_ver()
vals = system, node, release, version, machine
# Replace 'unknown' values with the more portable ''
@@ -1350,6 +1444,12 @@ def platform(aliased=False, terse=False):
# macOS and iOS both report as a "Darwin" kernel
if sys.platform == "ios":
system, release, _, _ = ios_ver()
+ elif sys.platform == "tvos":
+ system, release, _, _ = tvos_ver()
+ elif sys.platform == "watchos":
+ system, release, _, _ = watchos_ver()
+ elif sys.platform == "visionos":
+ system, release, _, _ = visionos_ver()
else:
macos_release = mac_ver()[0]
if macos_release:
diff --git a/Lib/site.py b/Lib/site.py
index f93271971594d8..74899abecb0aa7 100644
--- a/Lib/site.py
+++ b/Lib/site.py
@@ -298,8 +298,8 @@ def _getuserbase():
if env_base:
return env_base
- # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories
- if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
+ # Emscripten, iOS, tvOS, visionOS, VxWorks, WASI, and watchOS have no home directories
+ if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "visionos", "wasi", "watchos"}:
return None
def joinuser(*args):
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index 54c2eb515b60da..03896a234bf6ef 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -75,7 +75,7 @@
_mswindows = True
# some platforms do not support subprocesses
-_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos"}
+_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos", "visionos"}
if _mswindows:
import _winapi
diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py
index f93b98dd681536..0db3dbdce050ff 100644
--- a/Lib/sysconfig/__init__.py
+++ b/Lib/sysconfig/__init__.py
@@ -23,6 +23,9 @@
_ALWAYS_STR = {
'IPHONEOS_DEPLOYMENT_TARGET',
'MACOSX_DEPLOYMENT_TARGET',
+ 'TVOS_DEPLOYMENT_TARGET',
+ 'WATCHOS_DEPLOYMENT_TARGET',
+ 'XROS_DEPLOYMENT_TARGET',
}
_INSTALL_SCHEMES = {
@@ -119,7 +122,7 @@ def _getuserbase():
# Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories.
# Use _PYTHON_HOST_PLATFORM to get the correct platform when cross-compiling.
system_name = os.environ.get('_PYTHON_HOST_PLATFORM', sys.platform).split('-')[0]
- if system_name in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
+ if system_name in {"emscripten", "ios", "tvos", "visionos", "vxworks", "wasi", "watchos"}:
return None
def joinuser(*args):
@@ -730,6 +733,18 @@ def get_platform():
release = get_config_vars().get("IPHONEOS_DEPLOYMENT_TARGET", "13.0")
osname = sys.platform
machine = sys.implementation._multiarch
+ elif sys.platform == "tvos":
+ release = get_config_vars().get("TVOS_DEPLOYMENT_TARGET", "9.0")
+ osname = sys.platform
+ machine = sys.implementation._multiarch
+ elif sys.platform == "watchos":
+ release = get_config_vars().get("WATCHOS_DEPLOYMENT_TARGET", "4.0")
+ osname = sys.platform
+ machine = sys.implementation._multiarch
+ elif sys.platform == "visionos":
+ release = get_config_vars().get("XROS_DEPLOYMENT_TARGET", "2.0")
+ osname = sys.platform
+ machine = sys.implementation._multiarch
else:
import _osx_support
osname, release, machine = _osx_support.get_platform_osx(
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 1b551254f86c32..8594f92c097f07 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -7159,9 +7159,9 @@ def test_datetime_from_timestamp(self):
self.assertEqual(dt_orig, dt_rt)
def test_type_check_in_subinterp(self):
- # iOS requires the use of the custom framework loader,
+ # Apple mobile platforms require the use of the custom framework loader,
# not the ExtensionFileLoader.
- if sys.platform == "ios":
+ if support.is_apple_mobile:
extension_loader = "AppleFrameworkLoader"
else:
extension_loader = "ExtensionFileLoader"
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index b7cd7940eb15b3..32243a49e7a407 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -558,7 +558,7 @@ def skip_android_selinux(name):
sys.platform == "android", f"Android blocks {name} with SELinux"
)
-if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}:
+if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos", "visionos"}:
unix_shell = '/system/bin/sh' if is_android else '/bin/sh'
else:
unix_shell = None
@@ -574,7 +574,7 @@ def skip_emscripten_stack_overflow():
def skip_wasi_stack_overflow():
return unittest.skipIf(is_wasi, "Exhausts stack on WASI")
-is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"}
+is_apple_mobile = sys.platform in {"ios", "tvos", "watchos", "visionos"}
is_apple = is_apple_mobile or sys.platform == "darwin"
has_fork_support = hasattr(os, "fork") and not (
diff --git a/Lib/test/test_ctypes/test_dllist.py b/Lib/test/test_ctypes/test_dllist.py
index 15603dc3d77972..bff6c0fb95f129 100644
--- a/Lib/test/test_ctypes/test_dllist.py
+++ b/Lib/test/test_ctypes/test_dllist.py
@@ -7,7 +7,7 @@
WINDOWS = os.name == "nt"
-APPLE = sys.platform in {"darwin", "ios", "tvos", "watchos"}
+APPLE = sys.platform in {"darwin", "ios", "tvos", "watchos", "visionos"}
if WINDOWS:
KNOWN_LIBRARIES = ["KERNEL32.DLL"]
diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py
index 719c4feace6544..92a831a9148cc4 100644
--- a/Lib/test/test_platform.py
+++ b/Lib/test/test_platform.py
@@ -271,13 +271,21 @@ def test_uname(self):
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
# returned.
if _ctypes:
- self.assertIn(res.system, {"iOS", "iPadOS"})
- self.assertEqual(res.release, platform.ios_ver().release)
+ if sys.platform == "ios":
+ # iPads also identify as iOS
+ self.assertIn(res.system, {"iOS", "iPadOS"})
+ else:
+ # All other platforms - sys.platform is the lower case
+ # form of system (e.g., visionOS->visionos)
+ self.assertEqual(res.system.lower(), sys.platform)
+ # Use the platform-specific version method
+ platform_ver = getattr(platform, f"{sys.platform}_ver")
+ self.assertEqual(res.release, platform_ver().release)
else:
self.assertEqual(res.system, "")
self.assertEqual(res.release, "")
diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py
index 4c3ea1cd8df13e..04a210e5c86848 100644
--- a/Lib/test/test_webbrowser.py
+++ b/Lib/test/test_webbrowser.py
@@ -236,7 +236,8 @@ def test_open_new_tab(self):
arguments=[f'openURL({URL},new-tab)'])
-@unittest.skipUnless(sys.platform == "ios", "Test only applicable to iOS")
+@unittest.skipUnless(sys.platform in {"ios", "visionOS"},
+ "Test only applicable to iOS and visionOS")
class IOSBrowserTest(unittest.TestCase):
def _obj_ref(self, *args):
# Construct a string representation of the arguments that can be used
diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
index f2e2394089d5a1..2efbbfb0014736 100644
--- a/Lib/webbrowser.py
+++ b/Lib/webbrowser.py
@@ -488,7 +488,8 @@ def register_standard_browsers():
# macOS can use below Unix support (but we prefer using the macOS
# specific stuff)
- if sys.platform == "ios":
+ if sys.platform in {"ios", "visionos"}:
+ # iOS and visionOS provide a browser; tvOS and watchOS don't.
register("iosbrowser", None, IOSBrowser(), preferred=True)
if sys.platform == "serenityos":
@@ -653,9 +654,10 @@ def open(self, url, new=0, autoraise=True):
return not rc
#
-# Platform support for iOS
+# Platform support for Apple Mobile platforms that provide a browser
+# (i.e., iOS and visionOS)
#
-if sys.platform == "ios":
+if sys.platform in {"ios", "visionos"}:
from _ios_support import objc
if objc:
# If objc exists, we know ctypes is also importable.
diff --git a/Makefile.pre.in b/Makefile.pre.in
index b5703fbe6ae974..651320d7164de5 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -209,6 +209,12 @@ MACOSX_DEPLOYMENT_TARGET=@CONFIGURE_MACOSX_DEPLOYMENT_TARGET@
# the build, and is only listed here so it will be included in sysconfigdata.
IPHONEOS_DEPLOYMENT_TARGET=@IPHONEOS_DEPLOYMENT_TARGET@
+# visionOS Deployment target is *actually* used during the build, by the
+# compiler shims; export.
+XROS_DEPLOYMENT_TARGET=@XROS_DEPLOYMENT_TARGET@
+@EXPORT_XROS_DEPLOYMENT_TARGET@export XROS_DEPLOYMENT_TARGET
+
+
# Option to install to strip binaries
STRIPFLAG=-s
@@ -2264,7 +2270,7 @@ testuniversal: all
# 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.
-XCFOLDER:=iOSTestbed.$(MULTIARCH).$(shell date +%s).$$PPID
+XCFOLDER-iOS:=iOSTestbed.$(MULTIARCH).$(shell date +%s).$$PPID
.PHONY: testios
testios:
@if test "$(MACHDEP)" != "ios"; then \
@@ -2284,11 +2290,71 @@ testios:
exit 1;\
fi
- # Clone the testbed project into the XCFOLDER
- $(PYTHON_FOR_BUILD) $(srcdir)/iOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)"
+ # Clone the testbed project into the XCFOLDER-iOS
+ $(PYTHON_FOR_BUILD) $(srcdir)/iOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER-iOS)"
+
+ # Run the testbed project
+ $(PYTHON_FOR_BUILD) "$(XCFOLDER-iOS)" run --verbose -- test -uall --single-process --rerun -W
+
+# Run the test suite on the visionOS simulator. Must be run on a macOS machine with
+# a full Xcode install that has an Apple Vision Pro 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.
+XCFOLDER-visionOS:=visionOSTestbed.$(MULTIARCH).$(shell date +%s).$$PPID
+.PHONY: testvisionos
+testvisionos:
+ @if test "$(MACHDEP)" != "visionos"; then \
+ echo "Cannot run the visionOS testbed for a non-visionOS build."; \
+ exit 1;\
+ fi
+ @if test "$(findstring -xrsimulator,$(MULTIARCH))" != "-xrsimulator"; then \
+ echo "Cannot run the visionOS testbed for non-simulator builds."; \
+ exit 1;\
+ fi
+ @if test $(PYTHONFRAMEWORK) != "Python"; then \
+ echo "Cannot run the visionOS testbed with a non-default framework name."; \
+ exit 1;\
+ fi
+ @if ! test -d $(PYTHONFRAMEWORKPREFIX); then \
+ echo "Cannot find a finalized visionOS Python.framework. Have you run 'make install' to finalize the framework build?"; \
+ exit 1;\
+ fi
+
+ # Clone the testbed project into the XCFOLDER-visionOS
+ $(PYTHON_FOR_BUILD) $(srcdir)/visionOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER-visionOS)"
+
+ # Run the testbed project
+ $(PYTHON_FOR_BUILD) "$(XCFOLDER-visionOS)" run --verbose -- test -uall --single-process --rerun -W
+
+# Run the test suite on the tvOS 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.
+XCFOLDER-tvOS:=tvOSTestbed.$(MULTIARCH).$(shell date +%s).$$PPID
+.PHONY: testtvos
+testtvos:
+ @if test "$(MACHDEP)" != "tvos"; then \
+ echo "Cannot run the tvOS testbed for a non-tvOS build."; \
+ exit 1;\
+ fi
+ @if test "$(findstring -appletvsimulator,$(MULTIARCH))" != "-appletvsimulator"; then \
+ echo "Cannot run the tvOS testbed for non-simulator builds."; \
+ exit 1;\
+ fi
+ @if test $(PYTHONFRAMEWORK) != "Python"; then \
+ echo "Cannot run the tvOS testbed with a non-default framework name."; \
+ exit 1;\
+ fi
+ @if ! test -d $(PYTHONFRAMEWORKPREFIX); then \
+ echo "Cannot find a finalized tvOS Python.framework. Have you run 'make install' to finalize the framework build?"; \
+ exit 1;\
+ fi
+
+ # Clone the testbed project into the XCFOLDER-tvOS
+ $(PYTHON_FOR_BUILD) $(srcdir)/tvOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER-tvOS)"
# Run the testbed project
- $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W
+ $(PYTHON_FOR_BUILD) "$(XCFOLDER-tvOS)" run --verbose -- test -uall --single-process --rerun -W
# Like test, but using --slow-ci which enables all test resources and use
# longer timeout. Run an optional pybuildbot.identify script to include
diff --git a/Misc/platform_triplet.c b/Misc/platform_triplet.c
index f5cd73bdea8333..6c1863c943b353 100644
--- a/Misc/platform_triplet.c
+++ b/Misc/platform_triplet.c
@@ -257,6 +257,32 @@ PLATFORM_TRIPLET=arm64-iphonesimulator
# else
PLATFORM_TRIPLET=arm64-iphoneos
# endif
+# elif defined(TARGET_OS_TV) && TARGET_OS_TV
+# if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+# if __x86_64__
+PLATFORM_TRIPLET=x86_64-appletvsimulator
+# else
+PLATFORM_TRIPLET=arm64-appletvsimulator
+# endif
+# else
+PLATFORM_TRIPLET=arm64-appletvos
+# endif
+# elif defined(TARGET_OS_WATCH) && TARGET_OS_WATCH
+# if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+# if __x86_64__
+PLATFORM_TRIPLET=x86_64-watchsimulator
+# else
+PLATFORM_TRIPLET=arm64-watchsimulator
+# endif
+# else
+PLATFORM_TRIPLET=arm64_32-watchos
+# endif
+# elif defined(TARGET_OS_VISION) && TARGET_OS_VISION
+# if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+PLATFORM_TRIPLET=arm64-xrsimulator
+# else
+PLATFORM_TRIPLET=arm64-xros
+# endif
// Older macOS SDKs do not define TARGET_OS_OSX
# elif !defined(TARGET_OS_OSX) || TARGET_OS_OSX
PLATFORM_TRIPLET=darwin
diff --git a/config.sub b/config.sub
index 1bb6a05dc11026..49febd56a37cc0 100755
--- a/config.sub
+++ b/config.sub
@@ -1743,7 +1743,7 @@ case $os in
| hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \
| sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \
| hiux* | abug | nacl* | netware* | windows* \
- | os9* | macos* | osx* | ios* | tvos* | watchos* \
+ | os9* | macos* | osx* | ios* | tvos* | watchos* | xros* \
| mpw* | magic* | mmixware* | mon960* | lnews* \
| amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \
| aos* | aros* | cloudabi* | sortix* | twizzler* \
@@ -1867,7 +1867,7 @@ case $kernel-$os-$obj in
;;
*-eabi*- | *-gnueabi*-)
;;
- ios*-simulator- | tvos*-simulator- | watchos*-simulator- )
+ ios*-simulator- | tvos*-simulator- | watchos*-simulator- | xros*-simulator-)
;;
none--*)
# None (no kernel, i.e. freestanding / bare metal),
diff --git a/configure b/configure
index 884f8a4b0680d9..7c93d36e71729b 100755
--- a/configure
+++ b/configure
@@ -982,6 +982,10 @@ LDFLAGS
CFLAGS
CC
HAS_XCRUN
+EXPORT_XROS_DEPLOYMENT_TARGET
+XROS_DEPLOYMENT_TARGET
+WATCHOS_DEPLOYMENT_TARGET
+TVOS_DEPLOYMENT_TARGET
IPHONEOS_DEPLOYMENT_TARGET
EXPORT_MACOSX_DEPLOYMENT_TARGET
CONFIGURE_MACOSX_DEPLOYMENT_TARGET
@@ -4116,6 +4120,15 @@ then
*-apple-ios*)
ac_sys_system=iOS
;;
+ *-apple-tvos*)
+ ac_sys_system=tvOS
+ ;;
+ *-apple-watchos*)
+ ac_sys_system=watchOS
+ ;;
+ *-apple-xros*)
+ ac_sys_system=visionOS
+ ;;
*-*-darwin*)
ac_sys_system=Darwin
;;
@@ -4197,7 +4210,7 @@ fi
# On cross-compile builds, configure will look for a host-specific compiler by
# prepending the user-provided host triple to the required binary name.
#
-# On iOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc",
+# On iOS/tvOS/watchOS/visionOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc",
# 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
@@ -4212,6 +4225,17 @@ if test -z "$AR"; then
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 ;;
+
+ aarch64-apple-tvos*-simulator) AR=arm64-apple-tvos-simulator-ar ;;
+ aarch64-apple-tvos*) AR=arm64-apple-tvos-ar ;;
+ x86_64-apple-tvos*-simulator) AR=x86_64-apple-tvos-simulator-ar ;;
+
+ aarch64-apple-watchos*-simulator) AR=arm64-apple-watchos-simulator-ar ;;
+ aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;;
+ x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;;
+
+ aarch64-apple-xros*-simulator) AR=arm64-apple-xros-simulator-ar ;;
+ aarch64-apple-xros*) AR=arm64-apple-xros-ar ;;
*)
esac
fi
@@ -4220,6 +4244,17 @@ if test -z "$CC"; then
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 ;;
+
+ aarch64-apple-tvos*-simulator) CC=arm64-apple-tvos-simulator-clang ;;
+ aarch64-apple-tvos*) CC=arm64-apple-tvos-clang ;;
+ x86_64-apple-tvos*-simulator) CC=x86_64-apple-tvos-simulator-clang ;;
+
+ aarch64-apple-watchos*-simulator) CC=arm64-apple-watchos-simulator-clang ;;
+ aarch64-apple-watchos*) CC=arm64_32-apple-watchos-clang ;;
+ x86_64-apple-watchos*-simulator) CC=x86_64-apple-watchos-simulator-clang ;;
+
+ aarch64-apple-xros*-simulator) CC=arm64-apple-xros-simulator-clang ;;
+ aarch64-apple-xros*) CC=arm64-apple-xros-clang ;;
*)
esac
fi
@@ -4228,6 +4263,17 @@ if test -z "$CPP"; then
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 ;;
+
+ aarch64-apple-tvos*-simulator) CPP=arm64-apple-tvos-simulator-cpp ;;
+ aarch64-apple-tvos*) CPP=arm64-apple-tvos-cpp ;;
+ x86_64-apple-tvos*-simulator) CPP=x86_64-apple-tvos-simulator-cpp ;;
+
+ aarch64-apple-watchos*-simulator) CPP=arm64-apple-watchos-simulator-cpp ;;
+ aarch64-apple-watchos*) CPP=arm64_32-apple-watchos-cpp ;;
+ x86_64-apple-watchos*-simulator) CPP=x86_64-apple-watchos-simulator-cpp ;;
+
+ aarch64-apple-xros*-simulator) CPP=arm64-apple-xros-simulator-cpp ;;
+ aarch64-apple-xros*) CPP=arm64-apple-xros-cpp ;;
*)
esac
fi
@@ -4236,6 +4282,17 @@ if test -z "$CXX"; then
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++ ;;
+
+ aarch64-apple-tvos*-simulator) CXX=arm64-apple-tvos-simulator-clang++ ;;
+ aarch64-apple-tvos*) CXX=arm64-apple-tvos-clang++ ;;
+ x86_64-apple-tvos*-simulator) CXX=x86_64-apple-tvos-simulator-clang++ ;;
+
+ aarch64-apple-watchos*-simulator) CXX=arm64-apple-watchos-simulator-clang++ ;;
+ aarch64-apple-watchos*) CXX=arm64_32-apple-watchos-clang++ ;;
+ x86_64-apple-watchos*-simulator) CXX=x86_64-apple-watchos-simulator-clang++ ;;
+
+ aarch64-apple-xros*-simulator) CXX=arm64-apple-xros-simulator-clang++ ;;
+ aarch64-apple-xros*) CXX=arm64-apple-xros-clang++ ;;
*)
esac
fi
@@ -4358,8 +4415,11 @@ then :
case $enableval in
yes)
case $ac_sys_system in
- Darwin) enableval=/Library/Frameworks ;;
- iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;;
+ Darwin) enableval=/Library/Frameworks ;;
+ iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;;
+ tvOS) enableval=tvOS/Frameworks/\$\(MULTIARCH\) ;;
+ watchOS) enableval=watchOS/Frameworks/\$\(MULTIARCH\) ;;
+ visionOS) enableval=visionOS/Frameworks/\$\(MULTIARCH\) ;;
*) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5
esac
esac
@@ -4368,6 +4428,9 @@ then :
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 ;;
+ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;;
+ visionOS) as_fn_error $? "visionOS builds must use --enable-framework" "$LINENO" 5 ;;
*)
PYTHONFRAMEWORK=
PYTHONFRAMEWORKDIR=no-framework
@@ -4474,6 +4537,51 @@ then :
ac_config_files="$ac_config_files iOS/Resources/Info.plist"
+ ;;
+ tvOS) :
+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
+ FRAMEWORKPYTHONW=
+ INSTALLTARGETS="libinstall inclinstall sharedinstall"
+
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
+ RESSRCDIR=tvOS/Resources
+
+ ac_config_files="$ac_config_files tvOS/Resources/Info.plist"
+
+ ;;
+ watchOS) :
+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
+ FRAMEWORKPYTHONW=
+ INSTALLTARGETS="libinstall inclinstall sharedinstall"
+
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
+ RESSRCDIR=watchOS/Resources
+
+ ac_config_files="$ac_config_files watchOS/Resources/Info.plist"
+
+ ;;
+ visionOS) :
+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
+ FRAMEWORKPYTHONW=
+ INSTALLTARGETS="libinstall inclinstall sharedinstall"
+
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
+ RESSRCDIR=visionOS/Resources
+
+ ac_config_files="$ac_config_files visionOS/Resources/Info.plist"
+
;;
*)
as_fn_error $? "Unknown platform for framework build" "$LINENO" 5
@@ -4485,6 +4593,9 @@ else case e in #(
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 ;;
+ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;;
+ visionOS) as_fn_error $? "visionOS builds must use --enable-framework" "$LINENO" 5 ;;
*)
PYTHONFRAMEWORK=
PYTHONFRAMEWORKDIR=no-framework
@@ -4539,8 +4650,8 @@ then :
case "$withval" in
yes)
case $ac_sys_system in
- Darwin|iOS)
- # iOS is able to share the macOS patch
+ Darwin|iOS|tvOS|watchOS|visionOS)
+ # iOS/tvOS/watchOS/visionOS is able to share the macOS patch
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 ;;
@@ -4558,8 +4669,8 @@ printf "%s\n" "applying custom app store compliance patch" >&6; }
else case e in #(
e)
case $ac_sys_system in
- iOS)
- # Always apply the compliance patch on iOS; we can use the macOS patch
+ iOS|tvOS|watchOS|visionOS)
+ # Always apply the compliance patch on iOS/tvOS/watchOS/visionOS; we can use the macOS patch
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; }
@@ -4577,6 +4688,8 @@ fi
+EXPORT_XROS_DEPLOYMENT_TARGET='#'
+
if test "$cross_compiling" = yes; then
case "$host" in
@@ -4614,6 +4727,78 @@ printf "%s\n" "$IPHONEOS_DEPLOYMENT_TARGET" >&6; }
;;
esac
;;
+ *-apple-tvos*)
+ _host_os=`echo $host | cut -d '-' -f3`
+ _host_device=`echo $host | cut -d '-' -f4`
+ _host_device=${_host_device:=os}
+
+ # TVOS_DEPLOYMENT_TARGET is the minimum supported tvOS version
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking tvOS deployment target" >&5
+printf %s "checking tvOS deployment target... " >&6; }
+ TVOS_DEPLOYMENT_TARGET=${_host_os:4}
+ TVOS_DEPLOYMENT_TARGET=${TVOS_DEPLOYMENT_TARGET:=12.0}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $TVOS_DEPLOYMENT_TARGET" >&5
+printf "%s\n" "$TVOS_DEPLOYMENT_TARGET" >&6; }
+
+ case "$host_cpu" in
+ aarch64)
+ _host_ident=${TVOS_DEPLOYMENT_TARGET}-arm64-appletv${_host_device}
+ ;;
+ *)
+ _host_ident=${TVOS_DEPLOYMENT_TARGET}-$host_cpu-appletv${_host_device}
+ ;;
+ esac
+ ;;
+ *-apple-watchos*)
+ _host_os=`echo $host | cut -d '-' -f3`
+ _host_device=`echo $host | cut -d '-' -f4`
+ _host_device=${_host_device:=os}
+
+ # WATCHOS_DEPLOYMENT_TARGET is the minimum supported watchOS version
+ { 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}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $WATCHOS_DEPLOYMENT_TARGET" >&5
+printf "%s\n" "$WATCHOS_DEPLOYMENT_TARGET" >&6; }
+
+ case "$host_cpu" in
+ aarch64)
+ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-arm64-watch${_host_device}
+ ;;
+ *)
+ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-$host_cpu-watch${_host_device}
+ ;;
+ esac
+ ;;
+ *-apple-xros*)
+ _host_os=`echo $host | cut -d '-' -f3`
+ _host_device=`echo $host | cut -d '-' -f4`
+ _host_device=${_host_device:=os}
+
+ # XROS_DEPLOYMENT_TARGET is the minimum supported visionOS version
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking visionOS deployment target" >&5
+printf %s "checking visionOS deployment target... " >&6; }
+ XROS_DEPLOYMENT_TARGET=${_host_os:8}
+ XROS_DEPLOYMENT_TARGET=${XROS_DEPLOYMENT_TARGET:=2.0}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $XROS_DEPLOYMENT_TARGET" >&5
+printf "%s\n" "$XROS_DEPLOYMENT_TARGET" >&6; }
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking exporting flag of visionOS deployment target" >&5
+printf %s "checking exporting flag of visionOS deployment target... " >&6; }
+ export XROS_DEPLOYMENT_TARGET
+ EXPORT_XROS_DEPLOYMENT_TARGET=''
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $EXPORT_XROS_DEPLOYMENT_TARGET" >&5
+printf "%s\n" "$EXPORT_XROS_DEPLOYMENT_TARGET" >&6; }
+
+ case "$host_cpu" in
+ aarch64)
+ _host_ident=${XROS_DEPLOYMENT_TARGET}-arm64-xr${_host_device}
+ ;;
+ *)
+ _host_ident=${XROS_DEPLOYMENT_TARGET}-$host_cpu-xr${_host_device}
+ ;;
+ esac
+ ;;
*-*-darwin*)
case "$host_cpu" in
arm*)
@@ -4704,9 +4889,15 @@ printf "%s\n" "#define _BSD_SOURCE 1" >>confdefs.h
define_xopen_source=no;;
Darwin/[12][0-9].*)
define_xopen_source=no;;
- # On iOS, defining _POSIX_C_SOURCE also disables platform specific features.
+ # On iOS/tvOS/watchOS/visionOS, defining _POSIX_C_SOURCE also disables platform specific features.
iOS/*)
define_xopen_source=no;;
+ tvOS/*)
+ define_xopen_source=no;;
+ watchOS/*)
+ define_xopen_source=no;;
+ visionOS/*)
+ define_xopen_source=no;;
# On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from
# defining NI_NUMERICHOST.
QNX/6.3.2)
@@ -4769,7 +4960,13 @@ fi
CONFIGURE_MACOSX_DEPLOYMENT_TARGET=
EXPORT_MACOSX_DEPLOYMENT_TARGET='#'
-# Record the value of IPHONEOS_DEPLOYMENT_TARGET enforced by the selected host triple.
+# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET /
+# WATCHOS_DEPLOYMENT_TARGET / XROS_DEPLOYMENT_TARGET enforced by the selected host triple.
+
+
+
+
+# XROS_DEPLOYMENT_TARGET should get exported
# checks for alternative programs
@@ -4810,6 +5007,16 @@ case $ac_sys_system in #(
as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"
as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"
;; #(
+ tvOS) :
+
+ as_fn_append CFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"
+ as_fn_append LDFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"
+ ;; #(
+ watchOS) :
+
+ as_fn_append CFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"
+ as_fn_append LDFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"
+ ;; #(
*) :
;;
esac
@@ -7179,6 +7386,12 @@ case $ac_sys_system in #(
MULTIARCH="" ;; #(
iOS) :
MULTIARCH="" ;; #(
+ tvOS) :
+ MULTIARCH="" ;; #(
+ watchOS) :
+ MULTIARCH="" ;; #(
+ visionOS) :
+ MULTIARCH="" ;; #(
FreeBSD*) :
MULTIARCH="" ;; #(
*) :
@@ -7199,7 +7412,7 @@ fi
printf "%s\n" "$MULTIARCH" >&6; }
case $ac_sys_system in #(
- iOS) :
+ iOS|tvOS|watchOS|visionOS) :
SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #(
*) :
SOABI_PLATFORM=$PLATFORM_TRIPLET
@@ -7250,6 +7463,18 @@ case $host/$ac_cv_cc_name in #(
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-watchos*-simulator/clang) :
+ PY_SUPPORT_TIER=3 ;; #(
+ arm64_32-apple-watchos*/clang) :
+ PY_SUPPORT_TIER=3 ;; #(
+ aarch64-apple-xros*-simulator/clang) :
+ PY_SUPPORT_TIER=3 ;; #(
+ aarch64-apple-xros*/clang) :
+ PY_SUPPORT_TIER=3 ;; #(
aarch64-*-linux-android/clang) :
PY_SUPPORT_TIER=3 ;; #(
x86_64-*-linux-android/clang) :
@@ -7686,7 +7911,7 @@ then
case $ac_sys_system in
Darwin)
LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';;
- iOS)
+ iOS|tvOS|watchOS|visionOS)
LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';;
*)
as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;;
@@ -7752,7 +7977,7 @@ printf "%s\n" "#define Py_ENABLE_SHARED 1" >>confdefs.h
BLDLIBRARY='-L. -lpython$(LDVERSION)'
RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
;;
- iOS)
+ iOS|tvOS|watchOS|visionOS)
LDLIBRARY='libpython$(LDVERSION).dylib'
;;
AIX*)
@@ -13574,7 +13799,7 @@ then
BLDSHARED="$LDSHARED"
fi
;;
- iOS/*)
+ iOS/*|tvOS/*|watchOS/*|visionOS/*)
LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
BLDSHARED="$LDSHARED"
@@ -13707,7 +13932,7 @@ then
Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";;
Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";;
# -u libsys_s pulls in all symbols in libsys
- Darwin/*|iOS/*)
+ Darwin/*|iOS/*|tvOS/*|watchOS/*|visionOS/*)
LINKFORSHARED="$extra_undefs -framework CoreFoundation"
# Issue #18075: the default maximum stack size (8MBytes) is too
@@ -13731,7 +13956,7 @@ printf "%s\n" "#define THREAD_STACK_SIZE 0x$stack_size" >>confdefs.h
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" = "watchOS" -o "$ac_sys_system" = "visionOS"; then
LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)'
fi
;;
@@ -15508,7 +15733,7 @@ then :
ctypes_malloc_closure=yes
;; #(
- iOS) :
+ iOS|tvOS|watchOS|visionOS) :
ctypes_malloc_closure=yes
;; #(
@@ -19260,12 +19485,6 @@ 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" "execv" "ac_cv_func_execv"
-if test "x$ac_cv_func_execv" = xyes
-then :
- printf "%s\n" "#define HAVE_EXECV 1" >>confdefs.h
-
fi
ac_fn_c_check_func "$LINENO" "explicit_bzero" "ac_cv_func_explicit_bzero"
if test "x$ac_cv_func_explicit_bzero" = xyes
@@ -19326,18 +19545,6 @@ if test "x$ac_cv_func_fexecve" = xyes
then :
printf "%s\n" "#define HAVE_FEXECVE 1" >>confdefs.h
-fi
-ac_fn_c_check_func "$LINENO" "fork" "ac_cv_func_fork"
-if test "x$ac_cv_func_fork" = xyes
-then :
- printf "%s\n" "#define HAVE_FORK 1" >>confdefs.h
-
-fi
-ac_fn_c_check_func "$LINENO" "fork1" "ac_cv_func_fork1"
-if test "x$ac_cv_func_fork1" = xyes
-then :
- printf "%s\n" "#define HAVE_FORK1 1" >>confdefs.h
-
fi
ac_fn_c_check_func "$LINENO" "fpathconf" "ac_cv_func_fpathconf"
if test "x$ac_cv_func_fpathconf" = xyes
@@ -19764,24 +19971,6 @@ if test "x$ac_cv_func_posix_openpt" = xyes
then :
printf "%s\n" "#define HAVE_POSIX_OPENPT 1" >>confdefs.h
-fi
-ac_fn_c_check_func "$LINENO" "posix_spawn" "ac_cv_func_posix_spawn"
-if test "x$ac_cv_func_posix_spawn" = xyes
-then :
- printf "%s\n" "#define HAVE_POSIX_SPAWN 1" >>confdefs.h
-
-fi
-ac_fn_c_check_func "$LINENO" "posix_spawnp" "ac_cv_func_posix_spawnp"
-if test "x$ac_cv_func_posix_spawnp" = xyes
-then :
- printf "%s\n" "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h
-
-fi
-ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_addclosefrom_np" "ac_cv_func_posix_spawn_file_actions_addclosefrom_np"
-if test "x$ac_cv_func_posix_spawn_file_actions_addclosefrom_np" = xyes
-then :
- printf "%s\n" "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP 1" >>confdefs.h
-
fi
ac_fn_c_check_func "$LINENO" "pread" "ac_cv_func_pread"
if test "x$ac_cv_func_pread" = xyes
@@ -20100,12 +20289,6 @@ if test "x$ac_cv_func_sigaction" = xyes
then :
printf "%s\n" "#define HAVE_SIGACTION 1" >>confdefs.h
-fi
-ac_fn_c_check_func "$LINENO" "sigaltstack" "ac_cv_func_sigaltstack"
-if test "x$ac_cv_func_sigaltstack" = xyes
-then :
- printf "%s\n" "#define HAVE_SIGALTSTACK 1" >>confdefs.h
-
fi
ac_fn_c_check_func "$LINENO" "sigfillset" "ac_cv_func_sigfillset"
if test "x$ac_cv_func_sigfillset" = xyes
@@ -20374,11 +20557,11 @@ fi
fi
-# iOS defines some system methods that can be linked (so they are
+# iOS/tvOS/watchOS/visionOS define some system methods that can be linked (so they are
# found by configure), but either raise a compilation error (because the
# header definition prevents usage - autoconf doesn't use the headers), or
# raise an error if used at runtime. Force these symbols off.
-if test "$ac_sys_system" != "iOS" ; then
+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" -a "$ac_sys_system" != "visionOS" ; then
ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy"
if test "x$ac_cv_func_getentropy" = xyes
then :
@@ -20400,6 +20583,53 @@ fi
fi
+# 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_EXECV 1" >>confdefs.h
+
+fi
+ac_fn_c_check_func "$LINENO" "fork" "ac_cv_func_fork"
+if test "x$ac_cv_func_fork" = xyes
+then :
+ printf "%s\n" "#define HAVE_FORK 1" >>confdefs.h
+
+fi
+ac_fn_c_check_func "$LINENO" "fork1" "ac_cv_func_fork1"
+if test "x$ac_cv_func_fork1" = xyes
+then :
+ printf "%s\n" "#define HAVE_FORK1 1" >>confdefs.h
+
+fi
+ac_fn_c_check_func "$LINENO" "posix_spawn" "ac_cv_func_posix_spawn"
+if test "x$ac_cv_func_posix_spawn" = xyes
+then :
+ printf "%s\n" "#define HAVE_POSIX_SPAWN 1" >>confdefs.h
+
+fi
+ac_fn_c_check_func "$LINENO" "posix_spawnp" "ac_cv_func_posix_spawnp"
+if test "x$ac_cv_func_posix_spawnp" = xyes
+then :
+ printf "%s\n" "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h
+
+fi
+ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_addclosefrom_np" "ac_cv_func_posix_spawn_file_actions_addclosefrom_np"
+if test "x$ac_cv_func_posix_spawn_file_actions_addclosefrom_np" = xyes
+then :
+ printf "%s\n" "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP 1" >>confdefs.h
+
+fi
+ac_fn_c_check_func "$LINENO" "sigaltstack" "ac_cv_func_sigaltstack"
+if test "x$ac_cv_func_sigaltstack" = xyes
+then :
+ printf "%s\n" "#define HAVE_SIGALTSTACK 1" >>confdefs.h
+
+fi
+
+fi
+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC options needed to detect all undeclared functions" >&5
printf %s "checking for $CC options needed to detect all undeclared functions... " >&6; }
if test ${ac_cv_c_undeclared_builtin_options+y}
@@ -23844,7 +24074,8 @@ fi
# check for openpty, login_tty, and forkpty
-
+# tvOS/watchOS have functions for tty, but can't use them
+if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then
for ac_func in openpty
do :
@@ -23958,7 +24189,7 @@ esac
fi
done
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing login_tty" >&5
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing login_tty" >&5
printf %s "checking for library containing login_tty... " >&6; }
if test ${ac_cv_search_login_tty+y}
then :
@@ -24141,6 +24372,7 @@ esac
fi
done
+fi
# check for long file support functions
ac_fn_c_check_func "$LINENO" "fseek64" "ac_cv_func_fseek64"
@@ -24406,10 +24638,10 @@ fi
done
-# On Android and iOS, clock_settime can be linked (so it is found by
+# On Android, iOS, tvOS, watchOS, and visionOS, clock_settime can be linked (so it is found by
# configure), but when used in an unprivileged process, it crashes rather than
# returning an error. Force the symbol off.
-if test "$ac_sys_system" != "Linux-android" && test "$ac_sys_system" != "iOS"
+if test "$ac_sys_system" != "Linux-android" -a "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" -a "$ac_sys_system" != "visionOS"
then
for ac_func in clock_settime
@@ -24726,7 +24958,7 @@ else case e in #(
e) if test "$cross_compiling" = yes
then :
-if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then
+if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS" || test "$ac_sys_system" = "tvOS" || test "$ac_sys_system" = "watchOS" || test "$ac_sys_system" = "visionOS"; then
ac_cv_buggy_getaddrinfo="no"
elif test "${enable_ipv6+set}" = set; then
ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6"
@@ -26748,8 +26980,8 @@ if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MA
LIBPYTHON="\$(BLDLIBRARY)"
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 = "watchOS" -o $ac_sys_system = "visionOS"; then
MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)"
fi
@@ -29619,7 +29851,7 @@ LIBS=$save_LIBS
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for device files" >&5
printf "%s\n" "$as_me: checking for device files" >&6;}
-if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then
+if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" -o "$ac_sys_system" = "visionOS" ; then
ac_cv_file__dev_ptmx=no
ac_cv_file__dev_ptc=no
else
@@ -30129,7 +30361,7 @@ else case e in #(
with_ensurepip=no ;; #(
WASI) :
with_ensurepip=no ;; #(
- iOS) :
+ iOS|tvOS|watchOS|visionOS) :
with_ensurepip=no ;; #(
*) :
with_ensurepip=upgrade
@@ -31078,7 +31310,7 @@ case "$ac_sys_system" in
SunOS*) _PYTHREAD_NAME_MAXLEN=31;;
NetBSD*) _PYTHREAD_NAME_MAXLEN=15;; # gh-131268
Darwin) _PYTHREAD_NAME_MAXLEN=63;;
- iOS) _PYTHREAD_NAME_MAXLEN=63;;
+ iOS|tvOS|watchOS|visionOS) _PYTHREAD_NAME_MAXLEN=63;;
FreeBSD*) _PYTHREAD_NAME_MAXLEN=19;; # gh-131268
OpenBSD*) _PYTHREAD_NAME_MAXLEN=23;; # gh-131268
*) _PYTHREAD_NAME_MAXLEN=;;
@@ -31110,7 +31342,7 @@ case $ac_sys_system in #(
;; #(
Darwin) :
;; #(
- iOS) :
+ iOS|tvOS|watchOS|visionOS) :
@@ -35272,6 +35504,9 @@ do
"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" ;;
"iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES iOS/Resources/Info.plist" ;;
+ "tvOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES tvOS/Resources/Info.plist" ;;
+ "watchOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES watchOS/Resources/Info.plist" ;;
+ "visionOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES visionOS/Resources/Info.plist" ;;
"Makefile.pre") CONFIG_FILES="$CONFIG_FILES Makefile.pre" ;;
"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 cf25148bad2077..7ab0609bf8ab3e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -330,6 +330,15 @@ then
*-apple-ios*)
ac_sys_system=iOS
;;
+ *-apple-tvos*)
+ ac_sys_system=tvOS
+ ;;
+ *-apple-watchos*)
+ ac_sys_system=watchOS
+ ;;
+ *-apple-xros*)
+ ac_sys_system=visionOS
+ ;;
*-*-darwin*)
ac_sys_system=Darwin
;;
@@ -405,7 +414,7 @@ AC_SUBST([host_exec_prefix])
# On cross-compile builds, configure will look for a host-specific compiler by
# prepending the user-provided host triple to the required binary name.
#
-# On iOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc",
+# On iOS/tvOS/watchOS/visionOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc",
# 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
@@ -420,6 +429,17 @@ if test -z "$AR"; then
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 ;;
+
+ aarch64-apple-tvos*-simulator) AR=arm64-apple-tvos-simulator-ar ;;
+ aarch64-apple-tvos*) AR=arm64-apple-tvos-ar ;;
+ x86_64-apple-tvos*-simulator) AR=x86_64-apple-tvos-simulator-ar ;;
+
+ aarch64-apple-watchos*-simulator) AR=arm64-apple-watchos-simulator-ar ;;
+ aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;;
+ x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;;
+
+ aarch64-apple-xros*-simulator) AR=arm64-apple-xros-simulator-ar ;;
+ aarch64-apple-xros*) AR=arm64-apple-xros-ar ;;
*)
esac
fi
@@ -428,6 +448,17 @@ if test -z "$CC"; then
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 ;;
+
+ aarch64-apple-tvos*-simulator) CC=arm64-apple-tvos-simulator-clang ;;
+ aarch64-apple-tvos*) CC=arm64-apple-tvos-clang ;;
+ x86_64-apple-tvos*-simulator) CC=x86_64-apple-tvos-simulator-clang ;;
+
+ aarch64-apple-watchos*-simulator) CC=arm64-apple-watchos-simulator-clang ;;
+ aarch64-apple-watchos*) CC=arm64_32-apple-watchos-clang ;;
+ x86_64-apple-watchos*-simulator) CC=x86_64-apple-watchos-simulator-clang ;;
+
+ aarch64-apple-xros*-simulator) CC=arm64-apple-xros-simulator-clang ;;
+ aarch64-apple-xros*) CC=arm64-apple-xros-clang ;;
*)
esac
fi
@@ -436,6 +467,17 @@ if test -z "$CPP"; then
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 ;;
+
+ aarch64-apple-tvos*-simulator) CPP=arm64-apple-tvos-simulator-cpp ;;
+ aarch64-apple-tvos*) CPP=arm64-apple-tvos-cpp ;;
+ x86_64-apple-tvos*-simulator) CPP=x86_64-apple-tvos-simulator-cpp ;;
+
+ aarch64-apple-watchos*-simulator) CPP=arm64-apple-watchos-simulator-cpp ;;
+ aarch64-apple-watchos*) CPP=arm64_32-apple-watchos-cpp ;;
+ x86_64-apple-watchos*-simulator) CPP=x86_64-apple-watchos-simulator-cpp ;;
+
+ aarch64-apple-xros*-simulator) CPP=arm64-apple-xros-simulator-cpp ;;
+ aarch64-apple-xros*) CPP=arm64-apple-xros-cpp ;;
*)
esac
fi
@@ -444,6 +486,17 @@ if test -z "$CXX"; then
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++ ;;
+
+ aarch64-apple-tvos*-simulator) CXX=arm64-apple-tvos-simulator-clang++ ;;
+ aarch64-apple-tvos*) CXX=arm64-apple-tvos-clang++ ;;
+ x86_64-apple-tvos*-simulator) CXX=x86_64-apple-tvos-simulator-clang++ ;;
+
+ aarch64-apple-watchos*-simulator) CXX=arm64-apple-watchos-simulator-clang++ ;;
+ aarch64-apple-watchos*) CXX=arm64_32-apple-watchos-clang++ ;;
+ x86_64-apple-watchos*-simulator) CXX=x86_64-apple-watchos-simulator-clang++ ;;
+
+ aarch64-apple-xros*-simulator) CXX=arm64-apple-xros-simulator-clang++ ;;
+ aarch64-apple-xros*) CXX=arm64-apple-xros-clang++ ;;
*)
esac
fi
@@ -558,8 +611,11 @@ AC_ARG_ENABLE([framework],
case $enableval in
yes)
case $ac_sys_system in
- Darwin) enableval=/Library/Frameworks ;;
- iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;;
+ Darwin) enableval=/Library/Frameworks ;;
+ iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;;
+ tvOS) enableval=tvOS/Frameworks/\$\(MULTIARCH\) ;;
+ watchOS) enableval=watchOS/Frameworks/\$\(MULTIARCH\) ;;
+ visionOS) enableval=visionOS/Frameworks/\$\(MULTIARCH\) ;;
*) AC_MSG_ERROR([Unknown platform for framework build])
esac
esac
@@ -568,6 +624,9 @@ AC_ARG_ENABLE([framework],
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]) ;;
+ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;;
+ visionOS) AC_MSG_ERROR([visionOS builds must use --enable-framework]) ;;
*)
PYTHONFRAMEWORK=
PYTHONFRAMEWORKDIR=no-framework
@@ -670,6 +729,48 @@ AC_ARG_ENABLE([framework],
AC_CONFIG_FILES([iOS/Resources/Info.plist])
;;
+ tvOS) :
+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
+ FRAMEWORKPYTHONW=
+ INSTALLTARGETS="libinstall inclinstall sharedinstall"
+
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
+ RESSRCDIR=tvOS/Resources
+
+ AC_CONFIG_FILES([tvOS/Resources/Info.plist])
+ ;;
+ watchOS) :
+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
+ FRAMEWORKPYTHONW=
+ INSTALLTARGETS="libinstall inclinstall sharedinstall"
+
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
+ RESSRCDIR=watchOS/Resources
+
+ AC_CONFIG_FILES([watchOS/Resources/Info.plist])
+ ;;
+ visionOS) :
+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
+ FRAMEWORKPYTHONW=
+ INSTALLTARGETS="libinstall inclinstall sharedinstall"
+
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
+ RESSRCDIR=visionOS/Resources
+
+ AC_CONFIG_FILES([visionOS/Resources/Info.plist])
+ ;;
*)
AC_MSG_ERROR([Unknown platform for framework build])
;;
@@ -678,6 +779,9 @@ AC_ARG_ENABLE([framework],
],[
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]) ;;
+ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;;
+ visionOS) AC_MSG_ERROR([visionOS builds must use --enable-framework]) ;;
*)
PYTHONFRAMEWORK=
PYTHONFRAMEWORKDIR=no-framework
@@ -730,8 +834,8 @@ AC_ARG_WITH(
case "$withval" in
yes)
case $ac_sys_system in
- Darwin|iOS)
- # iOS is able to share the macOS patch
+ Darwin|iOS|tvOS|watchOS|visionOS)
+ # iOS/tvOS/watchOS/visionOS is able to share the macOS patch
APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch"
;;
*) AC_MSG_ERROR([no default app store compliance patch available for $ac_sys_system]) ;;
@@ -745,8 +849,8 @@ AC_ARG_WITH(
esac
],[
case $ac_sys_system in
- iOS)
- # Always apply the compliance patch on iOS; we can use the macOS patch
+ iOS|tvOS|watchOS|visionOS)
+ # Always apply the compliance patch on iOS/tvOS/watchOS/visionOS; we can use the macOS patch
APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch"
AC_MSG_RESULT([applying default app store compliance patch])
;;
@@ -759,6 +863,8 @@ AC_ARG_WITH(
])
AC_SUBST([APP_STORE_COMPLIANCE_PATCH])
+EXPORT_XROS_DEPLOYMENT_TARGET='#'
+
AC_SUBST([_PYTHON_HOST_PLATFORM])
if test "$cross_compiling" = yes; then
case "$host" in
@@ -794,6 +900,70 @@ if test "$cross_compiling" = yes; then
;;
esac
;;
+ *-apple-tvos*)
+ _host_os=`echo $host | cut -d '-' -f3`
+ _host_device=`echo $host | cut -d '-' -f4`
+ _host_device=${_host_device:=os}
+
+ # TVOS_DEPLOYMENT_TARGET is the minimum supported tvOS version
+ AC_MSG_CHECKING([tvOS deployment target])
+ TVOS_DEPLOYMENT_TARGET=${_host_os:4}
+ TVOS_DEPLOYMENT_TARGET=${TVOS_DEPLOYMENT_TARGET:=12.0}
+ AC_MSG_RESULT([$TVOS_DEPLOYMENT_TARGET])
+
+ case "$host_cpu" in
+ aarch64)
+ _host_ident=${TVOS_DEPLOYMENT_TARGET}-arm64-appletv${_host_device}
+ ;;
+ *)
+ _host_ident=${TVOS_DEPLOYMENT_TARGET}-$host_cpu-appletv${_host_device}
+ ;;
+ esac
+ ;;
+ *-apple-watchos*)
+ _host_os=`echo $host | cut -d '-' -f3`
+ _host_device=`echo $host | cut -d '-' -f4`
+ _host_device=${_host_device:=os}
+
+ # 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}
+ AC_MSG_RESULT([$WATCHOS_DEPLOYMENT_TARGET])
+
+ case "$host_cpu" in
+ aarch64)
+ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-arm64-watch${_host_device}
+ ;;
+ *)
+ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-$host_cpu-watch${_host_device}
+ ;;
+ esac
+ ;;
+ *-apple-xros*)
+ _host_os=`echo $host | cut -d '-' -f3`
+ _host_device=`echo $host | cut -d '-' -f4`
+ _host_device=${_host_device:=os}
+
+ # XROS_DEPLOYMENT_TARGET is the minimum supported visionOS version
+ AC_MSG_CHECKING([visionOS deployment target])
+ XROS_DEPLOYMENT_TARGET=${_host_os:8}
+ XROS_DEPLOYMENT_TARGET=${XROS_DEPLOYMENT_TARGET:=2.0}
+ AC_MSG_RESULT([$XROS_DEPLOYMENT_TARGET])
+ AC_MSG_CHECKING([exporting flag of visionOS deployment target])
+ export XROS_DEPLOYMENT_TARGET
+ EXPORT_XROS_DEPLOYMENT_TARGET=''
+ AC_MSG_RESULT([$EXPORT_XROS_DEPLOYMENT_TARGET])
+
+ case "$host_cpu" in
+ aarch64)
+ _host_ident=${XROS_DEPLOYMENT_TARGET}-arm64-xr${_host_device}
+ ;;
+ *)
+ _host_ident=${XROS_DEPLOYMENT_TARGET}-$host_cpu-xr${_host_device}
+ ;;
+ esac
+ ;;
*-*-darwin*)
case "$host_cpu" in
arm*)
@@ -883,9 +1053,15 @@ case $ac_sys_system/$ac_sys_release in
define_xopen_source=no;;
Darwin/@<:@[12]@:>@@<:@0-9@:>@.*)
define_xopen_source=no;;
- # On iOS, defining _POSIX_C_SOURCE also disables platform specific features.
+ # On iOS/tvOS/watchOS/visionOS, defining _POSIX_C_SOURCE also disables platform specific features.
iOS/*)
define_xopen_source=no;;
+ tvOS/*)
+ define_xopen_source=no;;
+ watchOS/*)
+ define_xopen_source=no;;
+ visionOS/*)
+ define_xopen_source=no;;
# On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from
# defining NI_NUMERICHOST.
QNX/6.3.2)
@@ -944,8 +1120,14 @@ AC_SUBST([EXPORT_MACOSX_DEPLOYMENT_TARGET])
CONFIGURE_MACOSX_DEPLOYMENT_TARGET=
EXPORT_MACOSX_DEPLOYMENT_TARGET='#'
-# Record the value of IPHONEOS_DEPLOYMENT_TARGET enforced by the selected host triple.
+# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET /
+# WATCHOS_DEPLOYMENT_TARGET / XROS_DEPLOYMENT_TARGET enforced by the selected host triple.
AC_SUBST([IPHONEOS_DEPLOYMENT_TARGET])
+AC_SUBST([TVOS_DEPLOYMENT_TARGET])
+AC_SUBST([WATCHOS_DEPLOYMENT_TARGET])
+AC_SUBST([XROS_DEPLOYMENT_TARGET])
+# XROS_DEPLOYMENT_TARGET should get exported
+AC_SUBST([EXPORT_XROS_DEPLOYMENT_TARGET])
# checks for alternative programs
@@ -979,11 +1161,19 @@ AS_CASE([$host],
],
)
-dnl Add the compiler flag for the iOS minimum supported OS version.
+dnl Add the compiler flag for the iOS/tvOS/watchOS minimum supported OS
+dnl version. visionOS doesn't use an explicit -mxros-version-min option -
+dnl it encodes the min version into the target triple.
AS_CASE([$ac_sys_system],
[iOS], [
AS_VAR_APPEND([CFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"])
AS_VAR_APPEND([LDFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"])
+ ],[tvOS], [
+ AS_VAR_APPEND([CFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"])
+ AS_VAR_APPEND([LDFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"])
+ ],[watchOS], [
+ AS_VAR_APPEND([CFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"])
+ AS_VAR_APPEND([LDFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"])
],
)
@@ -1172,6 +1362,9 @@ AC_MSG_CHECKING([for multiarch])
AS_CASE([$ac_sys_system],
[Darwin*], [MULTIARCH=""],
[iOS], [MULTIARCH=""],
+ [tvOS], [MULTIARCH=""],
+ [watchOS], [MULTIARCH=""],
+ [visionOS], [MULTIARCH=""],
[FreeBSD*], [MULTIARCH=""],
[MULTIARCH=$($CC --print-multiarch 2>/dev/null)]
)
@@ -1193,7 +1386,7 @@ dnl will have multiple sysconfig modules (one for each CPU architecture), but
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],
- [iOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`],
+ [iOS|tvOS|watchOS|visionOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`],
[SOABI_PLATFORM=$PLATFORM_TRIPLET]
)
@@ -1227,6 +1420,12 @@ AS_CASE([$host/$ac_cv_cc_name],
[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-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-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-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on ARM64
[x86_64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on AMD64
@@ -1536,7 +1735,7 @@ then
case $ac_sys_system in
Darwin)
LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';;
- iOS)
+ iOS|tvOS|watchOS|visionOS)
LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';;
*)
AC_MSG_ERROR([Unknown platform for framework build]);;
@@ -1601,7 +1800,7 @@ if test $enable_shared = "yes"; then
BLDLIBRARY='-L. -lpython$(LDVERSION)'
RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
;;
- iOS)
+ iOS|tvOS|watchOS|visionOS)
LDLIBRARY='libpython$(LDVERSION).dylib'
;;
AIX*)
@@ -3470,7 +3669,7 @@ then
BLDSHARED="$LDSHARED"
fi
;;
- iOS/*)
+ iOS/*|tvOS/*|watchOS/*|visionOS/*)
LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
BLDSHARED="$LDSHARED"
@@ -3594,7 +3793,7 @@ then
Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";;
Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";;
# -u libsys_s pulls in all symbols in libsys
- Darwin/*|iOS/*)
+ Darwin/*|iOS/*|tvOS/*|watchOS/*|visionOS/*)
LINKFORSHARED="$extra_undefs -framework CoreFoundation"
# Issue #18075: the default maximum stack size (8MBytes) is too
@@ -3618,7 +3817,7 @@ then
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" = "watchOS" -o "$ac_sys_system" = "visionOS"; then
LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)'
fi
;;
@@ -4106,7 +4305,7 @@ AS_VAR_IF([have_libffi], [yes], [
dnl when do we need USING_APPLE_OS_LIBFFI?
ctypes_malloc_closure=yes
],
- [iOS], [
+ [iOS|tvOS|watchOS|visionOS], [
ctypes_malloc_closure=yes
],
[sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])]
@@ -5215,9 +5414,9 @@ fi
# checks for library functions
AC_CHECK_FUNCS([ \
accept4 alarm bind_textdomain_codeset chmod chown clock closefrom close_range confstr \
- copy_file_range ctermid dladdr dup dup3 execv explicit_bzero explicit_memset \
+ copy_file_range ctermid dladdr dup dup3 explicit_bzero explicit_memset \
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 \
getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \
getpeername getpgid getpid getppid getpriority _getpty \
@@ -5225,8 +5424,7 @@ AC_CHECK_FUNCS([ \
getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown linkat \
lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \
mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
- pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \
- posix_spawn_file_actions_addclosefrom_np \
+ pipe2 plock poll 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
@@ -5236,7 +5434,7 @@ AC_CHECK_FUNCS([ \
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 \
- setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \
+ setresuid setreuid setsid setuid setvbuf shutdown sigaction \
sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \
sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \
@@ -5251,12 +5449,20 @@ if test "$MACHDEP" != linux; then
AC_CHECK_FUNCS([lchmod])
fi
-# iOS defines some system methods that can be linked (so they are
+# iOS/tvOS/watchOS/visionOS define some system methods that can be linked (so they are
# found by configure), but either raise a compilation error (because the
# header definition prevents usage - autoconf doesn't use the headers), or
# raise an error if used at runtime. Force these symbols off.
-if test "$ac_sys_system" != "iOS" ; then
- AC_CHECK_FUNCS([getentropy getgroups system])
+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" -a "$ac_sys_system" != "visionOS" ; then
+ AC_CHECK_FUNCS([ getentropy getgroups system ])
+fi
+
+# 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_CHECK_FUNCS([ \
+ execv fork fork1 posix_spawn posix_spawnp posix_spawn_file_actions_addclosefrom_np \
+ sigaltstack \
+ ])
fi
AC_CHECK_DECL([dirfd],
@@ -5539,20 +5745,22 @@ PY_CHECK_FUNC([setgroups], [
])
# check for openpty, login_tty, and forkpty
-
-AC_CHECK_FUNCS([openpty], [],
- [AC_CHECK_LIB([util], [openpty],
- [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lutil"],
- [AC_CHECK_LIB([bsd], [openpty],
- [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lbsd"])])])
-AC_SEARCH_LIBS([login_tty], [util],
- [AC_DEFINE([HAVE_LOGIN_TTY], [1], [Define to 1 if you have the `login_tty' function.])]
-)
-AC_CHECK_FUNCS([forkpty], [],
- [AC_CHECK_LIB([util], [forkpty],
- [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lutil"],
- [AC_CHECK_LIB([bsd], [forkpty],
- [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lbsd"])])])
+# tvOS/watchOS have functions for tty, but can't use them
+if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then
+ AC_CHECK_FUNCS([openpty], [],
+ [AC_CHECK_LIB([util], [openpty],
+ [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lutil"],
+ [AC_CHECK_LIB([bsd], [openpty],
+ [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lbsd"])])])
+ AC_SEARCH_LIBS([login_tty], [util],
+ [AC_DEFINE([HAVE_LOGIN_TTY], [1], [Define to 1 if you have the `login_tty' function.])]
+ )
+ AC_CHECK_FUNCS([forkpty], [],
+ [AC_CHECK_LIB([util], [forkpty],
+ [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lutil"],
+ [AC_CHECK_LIB([bsd], [forkpty],
+ [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lbsd"])])])
+fi
# check for long file support functions
AC_CHECK_FUNCS([fseek64 fseeko fstatvfs ftell64 ftello statvfs])
@@ -5591,10 +5799,10 @@ AC_CHECK_FUNCS([clock_getres], [], [
])
])
-# On Android and iOS, clock_settime can be linked (so it is found by
+# On Android, iOS, tvOS, watchOS, and visionOS, clock_settime can be linked (so it is found by
# configure), but when used in an unprivileged process, it crashes rather than
# returning an error. Force the symbol off.
-if test "$ac_sys_system" != "Linux-android" && test "$ac_sys_system" != "iOS"
+if test "$ac_sys_system" != "Linux-android" -a "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" -a "$ac_sys_system" != "visionOS"
then
AC_CHECK_FUNCS([clock_settime], [], [
AC_CHECK_LIB([rt], [clock_settime], [
@@ -5752,7 +5960,7 @@ int main(void)
[ac_cv_buggy_getaddrinfo=no],
[ac_cv_buggy_getaddrinfo=yes],
[
-if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then
+if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS" || test "$ac_sys_system" = "tvOS" || test "$ac_sys_system" = "watchOS" || test "$ac_sys_system" = "visionOS"; then
ac_cv_buggy_getaddrinfo="no"
elif test "${enable_ipv6+set}" = set; then
ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6"
@@ -6345,8 +6553,8 @@ if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MA
LIBPYTHON="\$(BLDLIBRARY)"
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 = "watchOS" -o $ac_sys_system = "visionOS"; then
MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)"
fi
@@ -7005,7 +7213,7 @@ AC_MSG_NOTICE([checking for device files])
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.
-if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then
+if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" -o "$ac_sys_system" = "visionOS" ; then
ac_cv_file__dev_ptmx=no
ac_cv_file__dev_ptc=no
else
@@ -7307,7 +7515,7 @@ AC_ARG_WITH([ensurepip],
AS_CASE([$ac_sys_system],
[Emscripten], [with_ensurepip=no],
[WASI], [with_ensurepip=no],
- [iOS], [with_ensurepip=no],
+ [iOS|tvOS|watchOS|visionOS], [with_ensurepip=no],
[with_ensurepip=upgrade]
)
])
@@ -7694,7 +7902,7 @@ case "$ac_sys_system" in
SunOS*) _PYTHREAD_NAME_MAXLEN=31;;
NetBSD*) _PYTHREAD_NAME_MAXLEN=15;; # gh-131268
Darwin) _PYTHREAD_NAME_MAXLEN=63;;
- iOS) _PYTHREAD_NAME_MAXLEN=63;;
+ iOS|tvOS|watchOS|visionOS) _PYTHREAD_NAME_MAXLEN=63;;
FreeBSD*) _PYTHREAD_NAME_MAXLEN=19;; # gh-131268
OpenBSD*) _PYTHREAD_NAME_MAXLEN=23;; # gh-131268
*) _PYTHREAD_NAME_MAXLEN=;;
@@ -7719,7 +7927,7 @@ AS_CASE([$ac_sys_system],
[VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [termios], [grp])],
dnl The _scproxy module is available on macOS
[Darwin], [],
- [iOS], [
+ [iOS|tvOS|watchOS|visionOS], [
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/iOS/Resources/Info.plist.in b/iOS/Resources/Info.plist.in
index c3e261ecd9eff7..26ef7a95de4a1c 100644
--- a/iOS/Resources/Info.plist.in
+++ b/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/iOS/testbed/__main__.py b/iOS/testbed/__main__.py
index c05497ede3aa61..1146bf3b988cda 100644
--- a/iOS/testbed/__main__.py
+++ b/iOS/testbed/__main__.py
@@ -127,7 +127,7 @@ async def async_check_output(*args, **kwargs):
async def select_simulator_device():
# List the testing simulators, in JSON format
raw_json = await async_check_output(
- "xcrun", "simctl", "--set", "testing", "list", "-j"
+ "xcrun", "simctl", "list", "-j"
)
json_data = json.loads(raw_json)
diff --git a/tvOS/README.rst b/tvOS/README.rst
new file mode 100644
index 00000000000000..57d1f3632e2cbd
--- /dev/null
+++ b/tvOS/README.rst
@@ -0,0 +1,109 @@
+=====================
+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 something like::
+
+ $ export PATH="$(pwd)/tvOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
+ $ ./configure \
+ --enable-framework=/path/to/install \
+ --host=aarch64-apple-tvos \
+ --build=aarch64-apple-darwin \
+ --with-build-python=/path/to/python.exe
+ $ make
+ $ make install
+
+In this invocation:
+
+* ``/path/to/install`` is the location where the final Python.framework will be
+ output.
+
+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler
+ triple format. This will be one of:
+
+ - ``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.
+
+* ``--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 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.
+
+Using a framework-based Python on tvOS
+======================================
diff --git a/tvOS/Resources/Info.plist.in b/tvOS/Resources/Info.plist.in
new file mode 100644
index 00000000000000..ab3050804b8c58
--- /dev/null
+++ b/tvOS/Resources/Info.plist.in
@@ -0,0 +1,34 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ Python
+ CFBundleGetInfoString
+ Python Runtime and Library
+ CFBundleIdentifier
+ @PYTHONFRAMEWORKIDENTIFIER@
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ Python
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ %VERSION%
+ CFBundleLongVersionString
+ %VERSION%, (c) 2001-2024 Python Software Foundation.
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ CFBundleSupportedPlatforms
+
+ tvOS
+
+ MinimumOSVersion
+ @TVOS_DEPLOYMENT_TARGET@
+
+
diff --git a/tvOS/Resources/bin/arm64-apple-tvos-ar b/tvOS/Resources/bin/arm64-apple-tvos-ar
new file mode 100755
index 00000000000000..e302748a13c8b1
--- /dev/null
+++ b/tvOS/Resources/bin/arm64-apple-tvos-ar
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvos${TVOS_SDK_VERSION} ar "$@"
diff --git a/tvOS/Resources/bin/arm64-apple-tvos-clang b/tvOS/Resources/bin/arm64-apple-tvos-clang
new file mode 100755
index 00000000000000..bef66ed852e1dd
--- /dev/null
+++ b/tvOS/Resources/bin/arm64-apple-tvos-clang
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos "$@"
diff --git a/tvOS/Resources/bin/arm64-apple-tvos-clang++ b/tvOS/Resources/bin/arm64-apple-tvos-clang++
new file mode 100755
index 00000000000000..04ca4df9ff00e5
--- /dev/null
+++ b/tvOS/Resources/bin/arm64-apple-tvos-clang++
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos "$@"
diff --git a/tvOS/Resources/bin/arm64-apple-tvos-cpp b/tvOS/Resources/bin/arm64-apple-tvos-cpp
new file mode 100755
index 00000000000000..cb797b5a5308cd
--- /dev/null
+++ b/tvOS/Resources/bin/arm64-apple-tvos-cpp
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos -E "$@"
diff --git a/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar b/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar
new file mode 100755
index 00000000000000..87ef5015aae9b4
--- /dev/null
+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@"
diff --git a/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang b/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang
new file mode 100755
index 00000000000000..729f3756fbcab2
--- /dev/null
+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos-simulator "$@"
diff --git a/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++
new file mode 100755
index 00000000000000..f98b36a6c8f9b6
--- /dev/null
+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos-simulator "$@"
diff --git a/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp b/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp
new file mode 100755
index 00000000000000..40555262ec280e
--- /dev/null
+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos-simulator -E "$@"
diff --git a/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar
new file mode 100755
index 00000000000000..87ef5015aae9b4
--- /dev/null
+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@"
diff --git a/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang
new file mode 100755
index 00000000000000..27b93b55bd8b6d
--- /dev/null
+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos-simulator "$@"
diff --git a/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++
new file mode 100755
index 00000000000000..df083314351e96
--- /dev/null
+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target x86_64-apple-tvos-simulator "$@"
diff --git a/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp
new file mode 100755
index 00000000000000..ad0c98ac52c312
--- /dev/null
+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos-simulator -E "$@"
diff --git a/tvOS/Resources/dylib-Info-template.plist b/tvOS/Resources/dylib-Info-template.plist
new file mode 100644
index 00000000000000..a20d476fa7b552
--- /dev/null
+++ b/tvOS/Resources/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/tvOS/Resources/pyconfig.h b/tvOS/Resources/pyconfig.h
new file mode 100644
index 00000000000000..4acff2c6051637
--- /dev/null
+++ b/tvOS/Resources/pyconfig.h
@@ -0,0 +1,7 @@
+#ifdef __arm64__
+#include "pyconfig-arm64.h"
+#endif
+
+#ifdef __x86_64__
+#include "pyconfig-x86_64.h"
+#endif
diff --git a/tvOS/testbed/Python.xcframework/Info.plist b/tvOS/testbed/Python.xcframework/Info.plist
new file mode 100644
index 00000000000000..bc07ad6bc45397
--- /dev/null
+++ b/tvOS/testbed/Python.xcframework/Info.plist
@@ -0,0 +1,44 @@
+
+
+
+
+ AvailableLibraries
+
+
+ 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
+
+
+ CFBundlePackageType
+ XFWK
+ XCFrameworkFormatVersion
+ 1.0
+
+
diff --git a/tvOS/testbed/Python.xcframework/tvos-arm64/README b/tvOS/testbed/Python.xcframework/tvos-arm64/README
new file mode 100644
index 00000000000000..10a546c15bb512
--- /dev/null
+++ b/tvOS/testbed/Python.xcframework/tvos-arm64/README
@@ -0,0 +1,5 @@
+This directory is intentionally empty.
+
+It is used as a destination for --enable-framework when building for AppleTVOS device, or
+it could be utilized by the testbed utility to clone a slice here.
+
diff --git a/tvOS/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README b/tvOS/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README
new file mode 100644
index 00000000000000..c5fa4037aa127f
--- /dev/null
+++ b/tvOS/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README
@@ -0,0 +1,5 @@
+This directory is intentionally empty.
+
+It is used as a destination for --enable-framework when building for AppleTVOS simulator, or
+it could be utilized by the testbed utility to clone a slice here.
+
diff --git a/tvOS/testbed/__main__.py b/tvOS/testbed/__main__.py
new file mode 100644
index 00000000000000..989f68794b839f
--- /dev/null
+++ b/tvOS/testbed/__main__.py
@@ -0,0 +1,512 @@
+import argparse
+import asyncio
+import fcntl
+import json
+import os
+import plistlib
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+from contextlib import asynccontextmanager
+from datetime import datetime
+from pathlib import Path
+
+
+DECODE_ARGS = ("UTF-8", "backslashreplace")
+
+# The system log prefixes each line:
+# 2025-01-17 16:14:29.090 Df tvOSTestbed[23987:1fd393b4] (Python) ...
+# 2025-01-17 16:14:29.090 E tvOSTestbed[23987:1fd393b4] (Python) ...
+
+LOG_PREFIX_REGEX = re.compile(
+ r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD
+ r"\s+\d+:\d{2}:\d{2}\.\d+" # HH:MM:SS.sss
+ r"\s+\w+" # Df/E
+ r"\s+tvOSTestbed\[\d+:\w+\]" # Process/thread ID
+ r"\s+\(Python\)\s" # Logger name
+)
+
+
+# Work around a bug involving sys.exit and TaskGroups
+# (https://github.com/python/cpython/issues/101515).
+def exit(*args):
+ raise MySystemExit(*args)
+
+
+class MySystemExit(Exception):
+ pass
+
+
+class SimulatorLock:
+ # An fcntl-based filesystem lock that can be used to ensure that
+ def __init__(self, timeout):
+ self.filename = Path(tempfile.gettempdir()) / "python-tvos-testbed"
+ self.timeout = timeout
+
+ self.fd = None
+
+ async def acquire(self):
+ # Ensure the lockfile exists
+ self.filename.touch(exist_ok=True)
+
+ # Try `timeout` times to acquire the lock file, with a 1 second pause
+ # between each attempt. Report status every 10 seconds.
+ for i in range(0, self.timeout):
+ try:
+ fd = os.open(self.filename, os.O_RDWR | os.O_TRUNC, 0o644)
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except OSError:
+ os.close(fd)
+ if i % 10 == 0:
+ print("... waiting", flush=True)
+ await asyncio.sleep(1)
+ else:
+ self.fd = fd
+ return
+
+ # If we reach the end of the loop, we've exceeded the allowed number of
+ # attempts.
+ raise ValueError("Unable to obtain lock on tvOS simulator creation")
+
+ def release(self):
+ # If a lock is held, release it.
+ if self.fd is not None:
+ # Release the lock.
+ fcntl.flock(self.fd, fcntl.LOCK_UN)
+ os.close(self.fd)
+ self.fd = None
+
+
+# All subprocesses are executed through this context manager so that no matter
+# what happens, they can always be cancelled from another task, and they will
+# always be cleaned up on exit.
+@asynccontextmanager
+async def async_process(*args, **kwargs):
+ process = await asyncio.create_subprocess_exec(*args, **kwargs)
+ try:
+ yield process
+ finally:
+ if process.returncode is None:
+ # Allow a reasonably long time for Xcode to clean itself up,
+ # because we don't want stale emulators left behind.
+ timeout = 10
+ process.terminate()
+ try:
+ await asyncio.wait_for(process.wait(), timeout)
+ except TimeoutError:
+ print(
+ f"Command {args} did not terminate after {timeout} seconds "
+ f" - sending SIGKILL"
+ )
+ process.kill()
+
+ # Even after killing the process we must still wait for it,
+ # otherwise we'll get the warning "Exception ignored in __del__".
+ await asyncio.wait_for(process.wait(), timeout=1)
+
+
+async def async_check_output(*args, **kwargs):
+ async with async_process(
+ *args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs
+ ) as process:
+ stdout, stderr = await process.communicate()
+ if process.returncode == 0:
+ return stdout.decode(*DECODE_ARGS)
+ else:
+ raise subprocess.CalledProcessError(
+ process.returncode,
+ args,
+ stdout.decode(*DECODE_ARGS),
+ stderr.decode(*DECODE_ARGS),
+ )
+
+
+# Return a list of UDIDs associated with booted simulators
+async def list_devices():
+ try:
+ # List the testing simulators, in JSON format
+ raw_json = await async_check_output(
+ "xcrun", "simctl", "--set", "testing", "list", "-j"
+ )
+ json_data = json.loads(raw_json)
+
+ # Filter out the booted tvOS simulators
+ return [
+ simulator["udid"]
+ for runtime, simulators in json_data["devices"].items()
+ for simulator in simulators
+ if runtime.split(".")[-1].startswith("tvOS") and simulator["state"] == "Booted"
+ ]
+ except subprocess.CalledProcessError as e:
+ # If there's no ~/Library/Developer/XCTestDevices folder (which is the
+ # case on fresh installs, and in some CI environments), `simctl list`
+ # returns error code 1, rather than an empty list. Handle that case,
+ # but raise all other errors.
+ if e.returncode == 1:
+ return []
+ else:
+ raise
+
+
+async def find_device(initial_devices, lock):
+ while True:
+ new_devices = set(await list_devices()).difference(initial_devices)
+ if len(new_devices) == 0:
+ await asyncio.sleep(1)
+ elif len(new_devices) == 1:
+ udid = new_devices.pop()
+ print(f"{datetime.now():%Y-%m-%d %H:%M:%S}: New test simulator detected")
+ print(f"UDID: {udid}", flush=True)
+ lock.release()
+ return udid
+ else:
+ exit(f"Found more than one new device: {new_devices}")
+
+
+async def log_stream_task(initial_devices, lock):
+ # Wait up to 5 minutes for the build to complete and the simulator to boot.
+ udid = await asyncio.wait_for(find_device(initial_devices, lock), 5 * 60)
+
+ # Stream the tvOS device's logs, filtering out messages that come from the
+ # XCTest test suite (catching NSLog messages from the test method), or
+ # Python itself (catching stdout/stderr content routed to the system log
+ # with config->use_system_logger).
+ args = [
+ "xcrun",
+ "simctl",
+ "--set",
+ "testing",
+ "spawn",
+ udid,
+ "log",
+ "stream",
+ "--style",
+ "compact",
+ "--predicate",
+ (
+ 'senderImagePath ENDSWITH "/tvOSTestbedTests.xctest/tvOSTestbedTests"'
+ ' OR senderImagePath ENDSWITH "/Python.framework/Python"'
+ ),
+ ]
+
+ async with async_process(
+ *args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ ) as process:
+ suppress_dupes = False
+ while line := (await process.stdout.readline()).decode(*DECODE_ARGS):
+ # Strip the prefix from each log line
+ line = LOG_PREFIX_REGEX.sub("", line)
+ # The tvOS log streamer can sometimes lag; when it does, it outputs
+ # a warning about messages being dropped... often multiple times.
+ # Only print the first of these duplicated warnings.
+ if line.startswith("=== Messages dropped "):
+ if not suppress_dupes:
+ suppress_dupes = True
+ sys.stdout.write(line)
+ else:
+ suppress_dupes = False
+ sys.stdout.write(line)
+ sys.stdout.flush()
+
+
+async def xcode_test(location, simulator, verbose):
+ # Run the test suite on the named simulator
+ print("Starting xcodebuild...", flush=True)
+ args = [
+ "xcodebuild",
+ "test",
+ "-project",
+ str(location / "tvOSTestbed.xcodeproj"),
+ "-scheme",
+ "tvOSTestbed",
+ "-destination",
+ f"platform=tvOS Simulator,name={simulator}",
+ "-resultBundlePath",
+ str(location / f"{datetime.now():%Y%m%d-%H%M%S}.xcresult"),
+ "-derivedDataPath",
+ str(location / "DerivedData"),
+ ]
+ if not verbose:
+ args += ["-quiet"]
+
+ async with async_process(
+ *args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ ) as process:
+ while line := (await process.stdout.readline()).decode(*DECODE_ARGS):
+ sys.stdout.write(line)
+ sys.stdout.flush()
+
+ status = await asyncio.wait_for(process.wait(), timeout=1)
+ exit(status)
+
+
+def clone_testbed(
+ source: Path,
+ target: Path,
+ framework: Path,
+ apps: list[Path],
+) -> None:
+ if target.exists():
+ print(f"{target} already exists; aborting without creating project.")
+ sys.exit(10)
+
+ if framework is None:
+ if not (
+ source / "Python.xcframework/tvos-arm64_x86_64-simulator/bin"
+ ).is_dir():
+ print(
+ f"The testbed being cloned ({source}) does not contain "
+ f"a simulator framework. Re-run with --framework"
+ )
+ sys.exit(11)
+ else:
+ if not framework.is_dir():
+ print(f"{framework} does not exist.")
+ sys.exit(12)
+ elif not (
+ framework.suffix == ".xcframework"
+ or (framework / "Python.framework").is_dir()
+ ):
+ print(
+ f"{framework} is not an XCframework, "
+ f"or a simulator slice of a framework build."
+ )
+ sys.exit(13)
+
+ print("Cloning testbed project:")
+ print(f" Cloning {source}...", end="", flush=True)
+ shutil.copytree(source, target, symlinks=True)
+ print(" done")
+
+ xc_framework_path = target / "Python.xcframework"
+ sim_framework_path = xc_framework_path / "tvos-arm64_x86_64-simulator"
+ if framework is not None:
+ if framework.suffix == ".xcframework":
+ print(" Installing XCFramework...", end="", flush=True)
+ if xc_framework_path.is_dir():
+ shutil.rmtree(xc_framework_path)
+ else:
+ xc_framework_path.unlink(missing_ok=True)
+ xc_framework_path.symlink_to(
+ framework.relative_to(xc_framework_path.parent, walk_up=True)
+ )
+ print(" done")
+ else:
+ print(" Installing simulator framework...", end="", flush=True)
+ if sim_framework_path.is_dir():
+ shutil.rmtree(sim_framework_path)
+ else:
+ sim_framework_path.unlink(missing_ok=True)
+ sim_framework_path.symlink_to(
+ framework.relative_to(sim_framework_path.parent, walk_up=True)
+ )
+ print(" done")
+ else:
+ if (
+ xc_framework_path.is_symlink()
+ and not xc_framework_path.readlink().is_absolute()
+ ):
+ # XCFramework is a relative symlink. Rewrite the symlink relative
+ # to the new location.
+ print(" Rewriting symlink to XCframework...", end="", flush=True)
+ orig_xc_framework_path = (
+ source
+ / xc_framework_path.readlink()
+ ).resolve()
+ xc_framework_path.unlink()
+ xc_framework_path.symlink_to(
+ orig_xc_framework_path.relative_to(
+ xc_framework_path.parent, walk_up=True
+ )
+ )
+ print(" done")
+ elif (
+ sim_framework_path.is_symlink()
+ and not sim_framework_path.readlink().is_absolute()
+ ):
+ print(" Rewriting symlink to simulator framework...", end="", flush=True)
+ # Simulator framework is a relative symlink. Rewrite the symlink
+ # relative to the new location.
+ orig_sim_framework_path = (
+ source
+ / "Python.XCframework"
+ / sim_framework_path.readlink()
+ ).resolve()
+ sim_framework_path.unlink()
+ sim_framework_path.symlink_to(
+ orig_sim_framework_path.relative_to(
+ sim_framework_path.parent, walk_up=True
+ )
+ )
+ print(" done")
+ else:
+ print(" Using pre-existing tvOS framework.")
+
+ for app_src in apps:
+ print(f" Installing app {app_src.name!r}...", end="", flush=True)
+ app_target = target / f"tvOSTestbed/app/{app_src.name}"
+ if app_target.is_dir():
+ shutil.rmtree(app_target)
+ shutil.copytree(app_src, app_target)
+ print(" done")
+
+ print(f"Successfully cloned testbed: {target.resolve()}")
+
+
+def update_plist(testbed_path, args):
+ # Add the test runner arguments to the testbed's Info.plist file.
+ info_plist = testbed_path / "tvOSTestbed" / "tvOSTestbed-Info.plist"
+ with info_plist.open("rb") as f:
+ info = plistlib.load(f)
+
+ info["TestArgs"] = args
+
+ with info_plist.open("wb") as f:
+ plistlib.dump(info, f)
+
+
+async def run_testbed(simulator: str, args: list[str], verbose: bool=False):
+ location = Path(__file__).parent
+ print("Updating plist...", end="", flush=True)
+ update_plist(location, args)
+ print(" done.", flush=True)
+
+ # We need to get an exclusive lock on simulator creation, to avoid issues
+ # with multiple simulators starting and being unable to tell which
+ # simulator is due to which testbed instance. See
+ # https://github.com/python/cpython/issues/130294 for details. Wait up to
+ # 10 minutes for a simulator to boot.
+ print("Obtaining lock on simulator creation...", flush=True)
+ simulator_lock = SimulatorLock(timeout=10*60)
+ await simulator_lock.acquire()
+ print("Simulator lock acquired.", flush=True)
+
+ # Get the list of devices that are booted at the start of the test run.
+ # The simulator started by the test suite will be detected as the new
+ # entry that appears on the device list.
+ initial_devices = await list_devices()
+
+ try:
+ async with asyncio.TaskGroup() as tg:
+ tg.create_task(log_stream_task(initial_devices, simulator_lock))
+ tg.create_task(xcode_test(location, simulator=simulator, verbose=verbose))
+ except* MySystemExit as e:
+ raise SystemExit(*e.exceptions[0].args) from None
+ except* subprocess.CalledProcessError as e:
+ # Extract it from the ExceptionGroup so it can be handled by `main`.
+ raise e.exceptions[0]
+ finally:
+ simulator_lock.release()
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=(
+ "Manages the process of testing a Python project in the tvOS simulator."
+ ),
+ )
+
+ subcommands = parser.add_subparsers(dest="subcommand")
+
+ clone = subcommands.add_parser(
+ "clone",
+ description=(
+ "Clone the testbed project, copying in an tvOS Python framework and"
+ "any specified application code."
+ ),
+ help="Clone a testbed project to a new location.",
+ )
+ clone.add_argument(
+ "--framework",
+ help=(
+ "The location of the XCFramework (or simulator-only slice of an "
+ "XCFramework) to use when running the testbed"
+ ),
+ )
+ clone.add_argument(
+ "--app",
+ dest="apps",
+ action="append",
+ default=[],
+ help="The location of any code to include in the testbed project",
+ )
+ clone.add_argument(
+ "location",
+ help="The path where the testbed will be cloned.",
+ )
+
+ run = subcommands.add_parser(
+ "run",
+ usage="%(prog)s [-h] [--simulator SIMULATOR] -- [ ...]",
+ description=(
+ "Run a testbed project. The arguments provided after `--` will be "
+ "passed to the running tvOS process as if they were arguments to "
+ "`python -m`."
+ ),
+ help="Run a testbed project",
+ )
+ run.add_argument(
+ "--simulator",
+ default="Apple TV",
+ help="The name of the simulator to use (default: 'Apple TV')",
+ )
+ run.add_argument(
+ "-v", "--verbose",
+ action="store_true",
+ help="Enable verbose output",
+ )
+
+ try:
+ pos = sys.argv.index("--")
+ testbed_args = sys.argv[1:pos]
+ test_args = sys.argv[pos + 1 :]
+ except ValueError:
+ testbed_args = sys.argv[1:]
+ test_args = []
+
+ context = parser.parse_args(testbed_args)
+
+ if context.subcommand == "clone":
+ clone_testbed(
+ source=Path(__file__).parent.resolve(),
+ target=Path(context.location).resolve(),
+ framework=Path(context.framework).resolve() if context.framework else None,
+ apps=[Path(app) for app in context.apps],
+ )
+ elif context.subcommand == "run":
+ if test_args:
+ if not (
+ Path(__file__).parent / "Python.xcframework/tvos-arm64_x86_64-simulator/bin"
+ ).is_dir():
+ print(
+ f"Testbed does not contain a compiled tvOS framework. Use "
+ f"`python {sys.argv[0]} clone ...` to create a runnable "
+ f"clone of this testbed."
+ )
+ sys.exit(20)
+
+ asyncio.run(
+ run_testbed(
+ simulator=context.simulator,
+ verbose=context.verbose,
+ args=test_args,
+ )
+ )
+ else:
+ print(f"Must specify test arguments (e.g., {sys.argv[0]} run -- test)")
+ print()
+ parser.print_help(sys.stderr)
+ sys.exit(21)
+ else:
+ parser.print_help(sys.stderr)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tvOS/testbed/app/README b/tvOS/testbed/app/README
new file mode 100644
index 00000000000000..af22c685f87976
--- /dev/null
+++ b/tvOS/testbed/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.
+
+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/tvOS/testbed/app_packages/README b/tvOS/testbed/app_packages/README
new file mode 100644
index 00000000000000..42d7fdeb813250
--- /dev/null
+++ b/tvOS/testbed/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.
+
+When the test suite runs, this folder will be on the PYTHONPATH.
diff --git a/tvOS/testbed/tvOSTestbed.xcodeproj/project.pbxproj b/tvOS/testbed/tvOSTestbed.xcodeproj/project.pbxproj
new file mode 100644
index 00000000000000..4e41033183fd83
--- /dev/null
+++ b/tvOS/testbed/tvOSTestbed.xcodeproj/project.pbxproj
@@ -0,0 +1,514 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ 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, ); }; };
+ EE7C8A2C2DCD7195003206DB /* app in Resources */ = {isa = PBXBuildFile; fileRef = EE7C8A2B2DCD7195003206DB /* app */; };
+ EE7C8A2E2DCD71A6003206DB /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = EE7C8A2D2DCD71A6003206DB /* app_packages */; };
+/* 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.xcframework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; };
+ EE7C8A2B2DCD7195003206DB /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = ""; };
+ EE7C8A2D2DCD71A6003206DB /* app_packages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app_packages; sourceTree = ""; };
+ EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tvOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ EE989E652DCD6E7A0036B268 /* tvOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = tvOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ EE989E502DCD6E780036B268 /* tvOSTestbed */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = tvOSTestbed;
+ sourceTree = "";
+ };
+ EE989E682DCD6E7A0036B268 /* tvOSTestbedTests */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = tvOSTestbedTests;
+ sourceTree = "";
+ };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ EE989E4B2DCD6E780036B268 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ EE7C8A1F2DCD70CD003206DB /* Python.xcframework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ EE989E622DCD6E7A0036B268 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ EE7C8A1E2DCD6FF3003206DB /* Python.xcframework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ EE989E452DCD6E780036B268 = {
+ isa = PBXGroup;
+ children = (
+ EE7C8A2D2DCD71A6003206DB /* app_packages */,
+ EE7C8A2B2DCD7195003206DB /* app */,
+ EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */,
+ EE989E502DCD6E780036B268 /* tvOSTestbed */,
+ EE989E682DCD6E7A0036B268 /* tvOSTestbedTests */,
+ EE989E4F2DCD6E780036B268 /* Products */,
+ );
+ sourceTree = "";
+ };
+ EE989E4F2DCD6E780036B268 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */,
+ EE989E652DCD6E7A0036B268 /* tvOSTestbedTests.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 /* Install Target Specific Python Standard Library */,
+ EE7C8A232DCD7149003206DB /* Prepare Python Binary Modules */,
+ EE7C8A212DCD70CD003206DB /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ EE989E502DCD6E780036B268 /* tvOSTestbed */,
+ );
+ name = tvOSTestbed;
+ packageProductDependencies = (
+ );
+ productName = tvOSTestbed;
+ productReference = EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */;
+ productType = "com.apple.product-type.application";
+ };
+ EE989E642DCD6E7A0036B268 /* tvOSTestbedTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = EE989E7C2DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "tvOSTestbedTests" */;
+ buildPhases = (
+ EE989E612DCD6E7A0036B268 /* Sources */,
+ EE989E622DCD6E7A0036B268 /* Frameworks */,
+ EE989E632DCD6E7A0036B268 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ EE989E672DCD6E7A0036B268 /* PBXTargetDependency */,
+ );
+ fileSystemSynchronizedGroups = (
+ EE989E682DCD6E7A0036B268 /* tvOSTestbedTests */,
+ );
+ name = tvOSTestbedTests;
+ packageProductDependencies = (
+ );
+ productName = tvOSTestbedTests;
+ productReference = EE989E652DCD6E7A0036B268 /* tvOSTestbedTests.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 /* tvOSTestbedTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ EE989E4C2DCD6E780036B268 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ EE7C8A2E2DCD71A6003206DB /* app_packages in Resources */,
+ EE7C8A2C2DCD7195003206DB /* app in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ EE989E632DCD6E7A0036B268 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ EE7C8A222DCD70F4003206DB /* Install Target Specific Python Standard Library */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = "Install Target Specific Python Standard Library";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "set -e\n\nmkdir -p \"$CODESIGNING_FOLDER_PATH/python/lib\"\nif [ \"$EFFECTIVE_PLATFORM_NAME\" = \"-appletvsimulator\" ]; then\n echo \"Installing Python modules for tvOS Simulator\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/tvos-arm64_x86_64-simulator/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nelse\n echo \"Installing Python modules for tvOS Device\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/tvos-arm64/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nfi\n";
+ };
+ EE7C8A232DCD7149003206DB /* Prepare Python Binary Modules */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = "Prepare Python Binary Modules";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "set -e\n\ninstall_dylib () {\n INSTALL_BASE=$1\n FULL_EXT=$2\n\n # The name of the extension file\n EXT=$(basename \"$FULL_EXT\")\n # The location of the extension file, relative to the bundle\n RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} \n # The path to the extension file, relative to the install base\n PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}\n # The full dotted name of the extension module, constructed from the file path.\n FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d \".\" -f 1 | tr \"/\" \".\"); \n # A bundle identifier; not actually used, but required by Xcode framework packaging\n FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr \"_\" \"-\")\n # The name of the framework folder.\n FRAMEWORK_FOLDER=\"Frameworks/$FULL_MODULE_NAME.framework\"\n\n # If the framework folder doesn't exist, create it.\n if [ ! -d \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\" ]; then\n echo \"Creating framework for $RELATIVE_EXT\" \n mkdir -p \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\"\n cp \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleExecutable -string \"$FULL_MODULE_NAME\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleIdentifier -string \"$FRAMEWORK_BUNDLE_ID\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n fi\n \n echo \"Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" \n mv \"$FULL_EXT\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\"\n # Create a placeholder .fwork file where the .so was\n echo \"$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" > ${FULL_EXT%.so}.fwork\n # Create a back reference to the .so file location in the framework\n echo \"${RELATIVE_EXT%.so}.fwork\" > \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin\" \n}\n\nPYTHON_VER=$(ls -1 \"$CODESIGNING_FOLDER_PATH/python/lib\")\necho \"Install Python $PYTHON_VER standard library extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib python/lib/$PYTHON_VER/lib-dynload/ \"$FULL_EXT\"\ndone\necho \"Install app package extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app_packages\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app_packages/ \"$FULL_EXT\"\ndone\necho \"Install app extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app/ \"$FULL_EXT\"\ndone\n\n# Clean up dylib template \nrm -f \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\"\necho \"Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)...\"\nfind \"$CODESIGNING_FOLDER_PATH/Frameworks\" -name \"*.framework\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der \"{}\" \\; \n";
+ };
+/* 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.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.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.tvOSTestbedTests;
+ 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.tvOSTestbedTests;
+ 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 "tvOSTestbedTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ EE989E7D2DCD6E7A0036B268 /* Debug */,
+ EE989E7E2DCD6E7A0036B268 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = EE989E462DCD6E780036B268 /* Project object */;
+}
diff --git a/tvOS/testbed/tvOSTestbed/AppDelegate.h b/tvOS/testbed/tvOSTestbed/AppDelegate.h
new file mode 100644
index 00000000000000..112c9ed64b83fb
--- /dev/null
+++ b/tvOS/testbed/tvOSTestbed/AppDelegate.h
@@ -0,0 +1,11 @@
+//
+// AppDelegate.h
+// tvOSTestbed
+//
+
+#import
+
+@interface AppDelegate : UIResponder
+
+
+@end
diff --git a/tvOS/testbed/tvOSTestbed/AppDelegate.m b/tvOS/testbed/tvOSTestbed/AppDelegate.m
new file mode 100644
index 00000000000000..bd91fb2d7d6d93
--- /dev/null
+++ b/tvOS/testbed/tvOSTestbed/AppDelegate.m
@@ -0,0 +1,19 @@
+//
+// AppDelegate.m
+// tvOSTestbed
+//
+
+#import "AppDelegate.h"
+
+@interface AppDelegate ()
+
+@end
+
+@implementation AppDelegate
+
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ return YES;
+}
+
+@end
diff --git a/tvOS/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard b/tvOS/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 00000000000000..660ba53de4f73d
--- /dev/null
+++ b/tvOS/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tvOS/testbed/tvOSTestbed/dylib-Info-template.plist b/tvOS/testbed/tvOSTestbed/dylib-Info-template.plist
new file mode 100644
index 00000000000000..f652e272f71c88
--- /dev/null
+++ b/tvOS/testbed/tvOSTestbed/dylib-Info-template.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+
+ CFBundleIdentifier
+
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSupportedPlatforms
+
+ iPhoneOS
+
+ MinimumOSVersion
+ 12.0
+ CFBundleVersion
+ 1
+
+
diff --git a/tvOS/testbed/tvOSTestbed/main.m b/tvOS/testbed/tvOSTestbed/main.m
new file mode 100644
index 00000000000000..d5808fbb9331b7
--- /dev/null
+++ b/tvOS/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/tvOS/testbed/tvOSTestbed/tvOSTestbed-Info.plist b/tvOS/testbed/tvOSTestbed/tvOSTestbed-Info.plist
new file mode 100644
index 00000000000000..a582f42a212783
--- /dev/null
+++ b/tvOS/testbed/tvOSTestbed/tvOSTestbed-Info.plist
@@ -0,0 +1,64 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDisplayName
+ ${PRODUCT_NAME}
+ CFBundleExecutable
+ ${EXECUTABLE_NAME}
+ CFBundleIdentifier
+ org.python.iOSTestbed
+ 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
+
+ TestArgs
+
+ test
+ -uall
+ --single-process
+ --rerun
+ -W
+
+
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+ UISceneConfigurations
+
+
+
+
diff --git a/tvOS/testbed/tvOSTestbedTests/tvOSTestbedTests.m b/tvOS/testbed/tvOSTestbedTests/tvOSTestbedTests.m
new file mode 100644
index 00000000000000..21d5a2872c1295
--- /dev/null
+++ b/tvOS/testbed/tvOSTestbedTests/tvOSTestbedTests.m
@@ -0,0 +1,162 @@
+#import
+#import
+
+@interface iOSTestbedTests : XCTestCase
+
+@end
+
+@implementation iOSTestbedTests
+
+
+- (void)testPython {
+ const char **argv;
+ int exit_code;
+ int failed;
+ PyStatus status;
+ PyPreConfig preconfig;
+ PyConfig config;
+ PyObject *sys_module;
+ PyObject *sys_path_attr;
+ NSArray *test_args;
+ NSString *python_home;
+ NSString *path;
+ wchar_t *wtmp_str;
+
+ NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
+
+ // Set some other common environment indicators to disable color, as the
+ // Xcode log can't display color. Stdout will report that it is *not* a
+ // TTY.
+ setenv("NO_COLOR", "1", true);
+ setenv("PYTHON_COLORS", "0", true);
+
+ // Arguments to pass into the test suite runner.
+ // argv[0] must identify the process; any subsequent arg
+ // will be handled as if it were an argument to `python -m test`
+ test_args = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"TestArgs"];
+ if (test_args == NULL) {
+ NSLog(@"Unable to identify test arguments.");
+ }
+ argv = malloc(sizeof(char *) * ([test_args count] + 1));
+ argv[0] = "tvOSTestbed";
+ for (int i = 1; i < [test_args count]; i++) {
+ argv[i] = [[test_args objectAtIndex:i] UTF8String];
+ }
+ NSLog(@"Test command: %@", test_args);
+
+ // Generate an isolated Python configuration.
+ NSLog(@"Configuring isolated Python...");
+ PyPreConfig_InitIsolatedConfig(&preconfig);
+ PyConfig_InitIsolatedConfig(&config);
+
+ // Configure the Python interpreter:
+ // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale.
+ // See https://docs.python.org/3/library/os.html#python-utf-8-mode.
+ preconfig.utf8_mode = 1;
+ // Use the system logger for stdout/err
+ config.use_system_logger = 1;
+ // Don't buffer stdio. We want output to appears in the log immediately
+ config.buffered_stdio = 0;
+ // Don't write bytecode; we can't modify the app bundle
+ // after it has been signed.
+ config.write_bytecode = 0;
+ // Ensure that signal handlers are installed
+ config.install_signal_handlers = 1;
+ // Run the test module.
+ config.run_module = Py_DecodeLocale([[test_args objectAtIndex:0] UTF8String], NULL);
+ // For debugging - enable verbose mode.
+ // config.verbose = 1;
+
+ NSLog(@"Pre-initializing Python runtime...");
+ status = Py_PreInitialize(&preconfig);
+ if (PyStatus_Exception(status)) {
+ XCTFail(@"Unable to pre-initialize Python interpreter: %s", status.err_msg);
+ PyConfig_Clear(&config);
+ return;
+ }
+
+ // Set the home for the Python interpreter
+ python_home = [NSString stringWithFormat:@"%@/python", resourcePath, nil];
+ NSLog(@"PythonHome: %@", python_home);
+ wtmp_str = Py_DecodeLocale([python_home UTF8String], NULL);
+ status = PyConfig_SetString(&config, &config.home, wtmp_str);
+ if (PyStatus_Exception(status)) {
+ XCTFail(@"Unable to set PYTHONHOME: %s", status.err_msg);
+ PyConfig_Clear(&config);
+ return;
+ }
+ PyMem_RawFree(wtmp_str);
+
+ // Read the site config
+ status = PyConfig_Read(&config);
+ if (PyStatus_Exception(status)) {
+ XCTFail(@"Unable to read site config: %s", status.err_msg);
+ PyConfig_Clear(&config);
+ return;
+ }
+
+ NSLog(@"Configure argc/argv...");
+ status = PyConfig_SetBytesArgv(&config, [test_args count], (char**) argv);
+ if (PyStatus_Exception(status)) {
+ XCTFail(@"Unable to configure argc/argv: %s", status.err_msg);
+ PyConfig_Clear(&config);
+ return;
+ }
+
+ NSLog(@"Initializing Python runtime...");
+ status = Py_InitializeFromConfig(&config);
+ if (PyStatus_Exception(status)) {
+ XCTFail(@"Unable to initialize Python interpreter: %s", status.err_msg);
+ PyConfig_Clear(&config);
+ return;
+ }
+
+ sys_module = PyImport_ImportModule("sys");
+ if (sys_module == NULL) {
+ XCTFail(@"Could not import sys module");
+ return;
+ }
+
+ sys_path_attr = PyObject_GetAttrString(sys_module, "path");
+ if (sys_path_attr == NULL) {
+ XCTFail(@"Could not access sys.path");
+ return;
+ }
+
+ // Add the app packages path
+ path = [NSString stringWithFormat:@"%@/app_packages", resourcePath, nil];
+ NSLog(@"App packages path: %@", path);
+ wtmp_str = Py_DecodeLocale([path UTF8String], NULL);
+ failed = PyList_Insert(sys_path_attr, 0, PyUnicode_FromString([path UTF8String]));
+ if (failed) {
+ XCTFail(@"Unable to add app packages to sys.path");
+ return;
+ }
+ PyMem_RawFree(wtmp_str);
+
+ path = [NSString stringWithFormat:@"%@/app", resourcePath, nil];
+ NSLog(@"App path: %@", path);
+ wtmp_str = Py_DecodeLocale([path UTF8String], NULL);
+ failed = PyList_Insert(sys_path_attr, 0, PyUnicode_FromString([path UTF8String]));
+ if (failed) {
+ XCTFail(@"Unable to add app to sys.path");
+ return;
+ }
+ PyMem_RawFree(wtmp_str);
+
+ // Ensure the working directory is the app folder.
+ chdir([path UTF8String]);
+
+ // Start the test suite. Print a separator to differentiate Python startup logs from app logs
+ NSLog(@"---------------------------------------------------------------------------");
+
+ exit_code = Py_RunMain();
+ XCTAssertEqual(exit_code, 0, @"Test suite did not pass");
+
+ NSLog(@"---------------------------------------------------------------------------");
+
+ Py_Finalize();
+}
+
+
+@end
diff --git a/visionOS/Resources/Info.plist.in b/visionOS/Resources/Info.plist.in
new file mode 100644
index 00000000000000..2679440da676fa
--- /dev/null
+++ b/visionOS/Resources/Info.plist.in
@@ -0,0 +1,34 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ Python
+ CFBundleGetInfoString
+ Python Runtime and Library
+ CFBundleIdentifier
+ @PYTHONFRAMEWORKIDENTIFIER@
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ Python
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ %VERSION%
+ CFBundleLongVersionString
+ %VERSION%, (c) 2001-2023 Python Software Foundation.
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ %VERSION%
+ CFBundleSupportedPlatforms
+
+ XROS
+
+ MinimumOSVersion
+ @XROS_DEPLOYMENT_TARGET@
+
+
diff --git a/visionOS/Resources/bin/arm64-apple-xros-ar b/visionOS/Resources/bin/arm64-apple-xros-ar
new file mode 100755
index 00000000000000..9fd78a205f3ea1
--- /dev/null
+++ b/visionOS/Resources/bin/arm64-apple-xros-ar
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk xros${XROS_SDK_VERSION} ar "$@"
diff --git a/visionOS/Resources/bin/arm64-apple-xros-clang b/visionOS/Resources/bin/arm64-apple-xros-clang
new file mode 100755
index 00000000000000..9a1a757cbd0943
--- /dev/null
+++ b/visionOS/Resources/bin/arm64-apple-xros-clang
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk xros${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} "$@"
diff --git a/visionOS/Resources/bin/arm64-apple-xros-clang++ b/visionOS/Resources/bin/arm64-apple-xros-clang++
new file mode 100755
index 00000000000000..f64fcfc11cd87b
--- /dev/null
+++ b/visionOS/Resources/bin/arm64-apple-xros-clang++
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk xros${XROS_SDK_VERSION} clang++ -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} "$@"
diff --git a/visionOS/Resources/bin/arm64-apple-xros-cpp b/visionOS/Resources/bin/arm64-apple-xros-cpp
new file mode 100755
index 00000000000000..d6492eff052f85
--- /dev/null
+++ b/visionOS/Resources/bin/arm64-apple-xros-cpp
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk xros${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} -E "$@"
diff --git a/visionOS/Resources/bin/arm64-apple-xros-simulator-ar b/visionOS/Resources/bin/arm64-apple-xros-simulator-ar
new file mode 100755
index 00000000000000..b202330fb5d4ff
--- /dev/null
+++ b/visionOS/Resources/bin/arm64-apple-xros-simulator-ar
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk xrsimulator${XROS_SDK_VERSION} ar "$@"
diff --git a/visionOS/Resources/bin/arm64-apple-xros-simulator-clang b/visionOS/Resources/bin/arm64-apple-xros-simulator-clang
new file mode 100755
index 00000000000000..87b0aba0751dda
--- /dev/null
+++ b/visionOS/Resources/bin/arm64-apple-xros-simulator-clang
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator "$@"
diff --git a/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++ b/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++
new file mode 100755
index 00000000000000..c89d48f1cb83fa
--- /dev/null
+++ b/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang++ -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator "$@"
diff --git a/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp b/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp
new file mode 100755
index 00000000000000..91b2d6264327ba
--- /dev/null
+++ b/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk xrsimulator clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator -E "$@"
diff --git a/visionOS/Resources/dylib-Info-template.plist b/visionOS/Resources/dylib-Info-template.plist
new file mode 100644
index 00000000000000..e91673b4fbcbc4
--- /dev/null
+++ b/visionOS/Resources/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/visionOS/testbed/Python.xcframework/Info.plist b/visionOS/testbed/Python.xcframework/Info.plist
new file mode 100644
index 00000000000000..e75f76c8d94946
--- /dev/null
+++ b/visionOS/testbed/Python.xcframework/Info.plist
@@ -0,0 +1,43 @@
+
+
+
+
+ AvailableLibraries
+
+
+ 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
+
+
+ CFBundlePackageType
+ XFWK
+ XCFrameworkFormatVersion
+ 1.0
+
+
diff --git a/visionOS/testbed/Python.xcframework/xros-arm64-simulator/README b/visionOS/testbed/Python.xcframework/xros-arm64-simulator/README
new file mode 100644
index 00000000000000..7bb0ad4b6baa9c
--- /dev/null
+++ b/visionOS/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/visionOS/testbed/Python.xcframework/xros-arm64/README b/visionOS/testbed/Python.xcframework/xros-arm64/README
new file mode 100644
index 00000000000000..2fc2112e47757a
--- /dev/null
+++ b/visionOS/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/visionOS/testbed/__main__.py b/visionOS/testbed/__main__.py
new file mode 100644
index 00000000000000..5fcf7d128cead6
--- /dev/null
+++ b/visionOS/testbed/__main__.py
@@ -0,0 +1,512 @@
+import argparse
+import asyncio
+import fcntl
+import json
+import os
+import plistlib
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+from contextlib import asynccontextmanager
+from datetime import datetime
+from pathlib import Path
+
+
+DECODE_ARGS = ("UTF-8", "backslashreplace")
+
+# The system log prefixes each line:
+# 2025-01-17 16:14:29.090 Df visionOSTestbed[23987:1fd393b4] (Python) ...
+# 2025-01-17 16:14:29.090 E visionOSTestbed[23987:1fd393b4] (Python) ...
+
+LOG_PREFIX_REGEX = re.compile(
+ r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD
+ r"\s+\d+:\d{2}:\d{2}\.\d+" # HH:MM:SS.sss
+ r"\s+\w+" # Df/E
+ r"\s+visionOSTestbed\[\d+:\w+\]" # Process/thread ID
+ r"\s+\(Python\)\s" # Logger name
+)
+
+
+# Work around a bug involving sys.exit and TaskGroups
+# (https://github.com/python/cpython/issues/101515).
+def exit(*args):
+ raise MySystemExit(*args)
+
+
+class MySystemExit(Exception):
+ pass
+
+
+class SimulatorLock:
+ # An fcntl-based filesystem lock that can be used to ensure that
+ def __init__(self, timeout):
+ self.filename = Path(tempfile.gettempdir()) / "python-visionos-testbed"
+ self.timeout = timeout
+
+ self.fd = None
+
+ async def acquire(self):
+ # Ensure the lockfile exists
+ self.filename.touch(exist_ok=True)
+
+ # Try `timeout` times to acquire the lock file, with a 1 second pause
+ # between each attempt. Report status every 10 seconds.
+ for i in range(0, self.timeout):
+ try:
+ fd = os.open(self.filename, os.O_RDWR | os.O_TRUNC, 0o644)
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except OSError:
+ os.close(fd)
+ if i % 10 == 0:
+ print("... waiting", flush=True)
+ await asyncio.sleep(1)
+ else:
+ self.fd = fd
+ return
+
+ # If we reach the end of the loop, we've exceeded the allowed number of
+ # attempts.
+ raise ValueError("Unable to obtain lock on visionOS simulator creation")
+
+ def release(self):
+ # If a lock is held, release it.
+ if self.fd is not None:
+ # Release the lock.
+ fcntl.flock(self.fd, fcntl.LOCK_UN)
+ os.close(self.fd)
+ self.fd = None
+
+
+# All subprocesses are executed through this context manager so that no matter
+# what happens, they can always be cancelled from another task, and they will
+# always be cleaned up on exit.
+@asynccontextmanager
+async def async_process(*args, **kwargs):
+ process = await asyncio.create_subprocess_exec(*args, **kwargs)
+ try:
+ yield process
+ finally:
+ if process.returncode is None:
+ # Allow a reasonably long time for Xcode to clean itself up,
+ # because we don't want stale emulators left behind.
+ timeout = 10
+ process.terminate()
+ try:
+ await asyncio.wait_for(process.wait(), timeout)
+ except TimeoutError:
+ print(
+ f"Command {args} did not terminate after {timeout} seconds "
+ f" - sending SIGKILL"
+ )
+ process.kill()
+
+ # Even after killing the process we must still wait for it,
+ # otherwise we'll get the warning "Exception ignored in __del__".
+ await asyncio.wait_for(process.wait(), timeout=1)
+
+
+async def async_check_output(*args, **kwargs):
+ async with async_process(
+ *args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs
+ ) as process:
+ stdout, stderr = await process.communicate()
+ if process.returncode == 0:
+ return stdout.decode(*DECODE_ARGS)
+ else:
+ raise subprocess.CalledProcessError(
+ process.returncode,
+ args,
+ stdout.decode(*DECODE_ARGS),
+ stderr.decode(*DECODE_ARGS),
+ )
+
+
+# Return a list of UDIDs associated with booted simulators
+async def list_devices():
+ try:
+ # List the testing simulators, in JSON format
+ raw_json = await async_check_output(
+ "xcrun", "simctl", "--set", "testing", "list", "-j"
+ )
+ json_data = json.loads(raw_json)
+
+ # Filter out the booted visionOS simulators
+ return [
+ simulator["udid"]
+ for runtime, simulators in json_data["devices"].items()
+ for simulator in simulators
+ if runtime.split(".")[-1].startswith("xrOS") and simulator["state"] == "Booted"
+ ]
+ except subprocess.CalledProcessError as e:
+ # If there's no ~/Library/Developer/XCTestDevices folder (which is the
+ # case on fresh installs, and in some CI environments), `simctl list`
+ # returns error code 1, rather than an empty list. Handle that case,
+ # but raise all other errors.
+ if e.returncode == 1:
+ return []
+ else:
+ raise
+
+
+async def find_device(initial_devices, lock):
+ while True:
+ new_devices = set(await list_devices()).difference(initial_devices)
+ if len(new_devices) == 0:
+ await asyncio.sleep(1)
+ elif len(new_devices) == 1:
+ udid = new_devices.pop()
+ print(f"{datetime.now():%Y-%m-%d %H:%M:%S}: New test simulator detected")
+ print(f"UDID: {udid}", flush=True)
+ lock.release()
+ return udid
+ else:
+ exit(f"Found more than one new device: {new_devices}")
+
+
+async def log_stream_task(initial_devices, lock):
+ # Wait up to 5 minutes for the build to complete and the simulator to boot.
+ udid = await asyncio.wait_for(find_device(initial_devices, lock), 5 * 60)
+
+ # Stream the visionOS device's logs, filtering out messages that come from the
+ # XCTest test suite (catching NSLog messages from the test method), or
+ # Python itself (catching stdout/stderr content routed to the system log
+ # with config->use_system_logger).
+ args = [
+ "xcrun",
+ "simctl",
+ "--set",
+ "testing",
+ "spawn",
+ udid,
+ "log",
+ "stream",
+ "--style",
+ "compact",
+ "--predicate",
+ (
+ 'senderImagePath ENDSWITH "/visionOSTestbedTests.xctest/visionOSTestbedTests"'
+ ' OR senderImagePath ENDSWITH "/Python.framework/Python"'
+ ),
+ ]
+
+ async with async_process(
+ *args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ ) as process:
+ suppress_dupes = False
+ while line := (await process.stdout.readline()).decode(*DECODE_ARGS):
+ # Strip the prefix from each log line
+ line = LOG_PREFIX_REGEX.sub("", line)
+ # The visionOS log streamer can sometimes lag; when it does, it outputs
+ # a warning about messages being dropped... often multiple times.
+ # Only print the first of these duplicated warnings.
+ if line.startswith("=== Messages dropped "):
+ if not suppress_dupes:
+ suppress_dupes = True
+ sys.stdout.write(line)
+ else:
+ suppress_dupes = False
+ sys.stdout.write(line)
+ sys.stdout.flush()
+
+
+async def xcode_test(location, simulator, verbose):
+ # Run the test suite on the named simulator
+ print("Starting xcodebuild...", flush=True)
+ args = [
+ "xcodebuild",
+ "test",
+ "-project",
+ str(location / "visionOSTestbed.xcodeproj"),
+ "-scheme",
+ "visionOSTestbed",
+ "-destination",
+ f"platform=visionOS Simulator,name={simulator}",
+ "-resultBundlePath",
+ str(location / f"{datetime.now():%Y%m%d-%H%M%S}.xcresult"),
+ "-derivedDataPath",
+ str(location / "DerivedData"),
+ ]
+ if not verbose:
+ args += ["-quiet"]
+
+ async with async_process(
+ *args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ ) as process:
+ while line := (await process.stdout.readline()).decode(*DECODE_ARGS):
+ sys.stdout.write(line)
+ sys.stdout.flush()
+
+ status = await asyncio.wait_for(process.wait(), timeout=1)
+ exit(status)
+
+
+def clone_testbed(
+ source: Path,
+ target: Path,
+ framework: Path,
+ apps: list[Path],
+) -> None:
+ if target.exists():
+ print(f"{target} already exists; aborting without creating project.")
+ sys.exit(10)
+
+ if framework is None:
+ if not (
+ source / "Python.xcframework/xros-arm64-simulator/bin"
+ ).is_dir():
+ print(
+ f"The testbed being cloned ({source}) does not contain "
+ f"a simulator framework. Re-run with --framework"
+ )
+ sys.exit(11)
+ else:
+ if not framework.is_dir():
+ print(f"{framework} does not exist.")
+ sys.exit(12)
+ elif not (
+ framework.suffix == ".xcframework"
+ or (framework / "Python.framework").is_dir()
+ ):
+ print(
+ f"{framework} is not an XCframework, "
+ f"or a simulator slice of a framework build."
+ )
+ sys.exit(13)
+
+ print("Cloning testbed project:")
+ print(f" Cloning {source}...", end="", flush=True)
+ shutil.copytree(source, target, symlinks=True)
+ print(" done")
+
+ xc_framework_path = target / "Python.xcframework"
+ sim_framework_path = xc_framework_path / "xros-arm64-simulator"
+ if framework is not None:
+ if framework.suffix == ".xcframework":
+ print(" Installing XCFramework...", end="", flush=True)
+ if xc_framework_path.is_dir():
+ shutil.rmtree(xc_framework_path)
+ else:
+ xc_framework_path.unlink(missing_ok=True)
+ xc_framework_path.symlink_to(
+ framework.relative_to(xc_framework_path.parent, walk_up=True)
+ )
+ print(" done")
+ else:
+ print(" Installing simulator framework...", end="", flush=True)
+ if sim_framework_path.is_dir():
+ shutil.rmtree(sim_framework_path)
+ else:
+ sim_framework_path.unlink(missing_ok=True)
+ sim_framework_path.symlink_to(
+ framework.relative_to(sim_framework_path.parent, walk_up=True)
+ )
+ print(" done")
+ else:
+ if (
+ xc_framework_path.is_symlink()
+ and not xc_framework_path.readlink().is_absolute()
+ ):
+ # XCFramework is a relative symlink. Rewrite the symlink relative
+ # to the new location.
+ print(" Rewriting symlink to XCframework...", end="", flush=True)
+ orig_xc_framework_path = (
+ source
+ / xc_framework_path.readlink()
+ ).resolve()
+ xc_framework_path.unlink()
+ xc_framework_path.symlink_to(
+ orig_xc_framework_path.relative_to(
+ xc_framework_path.parent, walk_up=True
+ )
+ )
+ print(" done")
+ elif (
+ sim_framework_path.is_symlink()
+ and not sim_framework_path.readlink().is_absolute()
+ ):
+ print(" Rewriting symlink to simulator framework...", end="", flush=True)
+ # Simulator framework is a relative symlink. Rewrite the symlink
+ # relative to the new location.
+ orig_sim_framework_path = (
+ source
+ / "Python.XCframework"
+ / sim_framework_path.readlink()
+ ).resolve()
+ sim_framework_path.unlink()
+ sim_framework_path.symlink_to(
+ orig_sim_framework_path.relative_to(
+ sim_framework_path.parent, walk_up=True
+ )
+ )
+ print(" done")
+ else:
+ print(" Using pre-existing visionOS framework.")
+
+ for app_src in apps:
+ print(f" Installing app {app_src.name!r}...", end="", flush=True)
+ app_target = target / f"visionOSTestbed/app/{app_src.name}"
+ if app_target.is_dir():
+ shutil.rmtree(app_target)
+ shutil.copytree(app_src, app_target)
+ print(" done")
+
+ print(f"Successfully cloned testbed: {target.resolve()}")
+
+
+def update_plist(testbed_path, args):
+ # Add the test runner arguments to the testbed's Info.plist file.
+ info_plist = testbed_path / "visionOSTestbed" / "visionOSTestbed-Info.plist"
+ with info_plist.open("rb") as f:
+ info = plistlib.load(f)
+
+ info["TestArgs"] = args
+
+ with info_plist.open("wb") as f:
+ plistlib.dump(info, f)
+
+
+async def run_testbed(simulator: str, args: list[str], verbose: bool=False):
+ location = Path(__file__).parent
+ print("Updating plist...", end="", flush=True)
+ update_plist(location, args)
+ print(" done.", flush=True)
+
+ # We need to get an exclusive lock on simulator creation, to avoid issues
+ # with multiple simulators starting and being unable to tell which
+ # simulator is due to which testbed instance. See
+ # https://github.com/python/cpython/issues/130294 for details. Wait up to
+ # 10 minutes for a simulator to boot.
+ print("Obtaining lock on simulator creation...", flush=True)
+ simulator_lock = SimulatorLock(timeout=10*60)
+ await simulator_lock.acquire()
+ print("Simulator lock acquired.", flush=True)
+
+ # Get the list of devices that are booted at the start of the test run.
+ # The simulator started by the test suite will be detected as the new
+ # entry that appears on the device list.
+ initial_devices = await list_devices()
+
+ try:
+ async with asyncio.TaskGroup() as tg:
+ tg.create_task(log_stream_task(initial_devices, simulator_lock))
+ tg.create_task(xcode_test(location, simulator=simulator, verbose=verbose))
+ except* MySystemExit as e:
+ raise SystemExit(*e.exceptions[0].args) from None
+ except* subprocess.CalledProcessError as e:
+ # Extract it from the ExceptionGroup so it can be handled by `main`.
+ raise e.exceptions[0]
+ finally:
+ simulator_lock.release()
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=(
+ "Manages the process of testing a Python project in the visionOS simulator."
+ ),
+ )
+
+ subcommands = parser.add_subparsers(dest="subcommand")
+
+ clone = subcommands.add_parser(
+ "clone",
+ description=(
+ "Clone the testbed project, copying in an visionOS Python framework and"
+ "any specified application code."
+ ),
+ help="Clone a testbed project to a new location.",
+ )
+ clone.add_argument(
+ "--framework",
+ help=(
+ "The location of the XCFramework (or simulator-only slice of an "
+ "XCFramework) to use when running the testbed"
+ ),
+ )
+ clone.add_argument(
+ "--app",
+ dest="apps",
+ action="append",
+ default=[],
+ help="The location of any code to include in the testbed project",
+ )
+ clone.add_argument(
+ "location",
+ help="The path where the testbed will be cloned.",
+ )
+
+ run = subcommands.add_parser(
+ "run",
+ usage="%(prog)s [-h] [--simulator SIMULATOR] -- [ ...]",
+ description=(
+ "Run a testbed project. The arguments provided after `--` will be "
+ "passed to the running visionOS process as if they were arguments to "
+ "`python -m`."
+ ),
+ help="Run a testbed project",
+ )
+ run.add_argument(
+ "--simulator",
+ default="Apple Vision Pro",
+ help="The name of the simulator to use (default: 'Apple Vision Pro')",
+ )
+ run.add_argument(
+ "-v", "--verbose",
+ action="store_true",
+ help="Enable verbose output",
+ )
+
+ try:
+ pos = sys.argv.index("--")
+ testbed_args = sys.argv[1:pos]
+ test_args = sys.argv[pos + 1 :]
+ except ValueError:
+ testbed_args = sys.argv[1:]
+ test_args = []
+
+ context = parser.parse_args(testbed_args)
+
+ if context.subcommand == "clone":
+ clone_testbed(
+ source=Path(__file__).parent.resolve(),
+ target=Path(context.location).resolve(),
+ framework=Path(context.framework).resolve() if context.framework else None,
+ apps=[Path(app) for app in context.apps],
+ )
+ elif context.subcommand == "run":
+ if test_args:
+ if not (
+ Path(__file__).parent / "Python.xcframework/xros-arm64-simulator/bin"
+ ).is_dir():
+ print(
+ f"Testbed does not contain a compiled visionOS framework. Use "
+ f"`python {sys.argv[0]} clone ...` to create a runnable "
+ f"clone of this testbed."
+ )
+ sys.exit(20)
+
+ asyncio.run(
+ run_testbed(
+ simulator=context.simulator,
+ verbose=context.verbose,
+ args=test_args,
+ )
+ )
+ else:
+ print(f"Must specify test arguments (e.g., {sys.argv[0]} run -- test)")
+ print()
+ parser.print_help(sys.stderr)
+ sys.exit(21)
+ else:
+ parser.print_help(sys.stderr)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/visionOS/testbed/visionOSTestbed.xcodeproj/project.pbxproj b/visionOS/testbed/visionOSTestbed.xcodeproj/project.pbxproj
new file mode 100644
index 00000000000000..1928369cac528b
--- /dev/null
+++ b/visionOS/testbed/visionOSTestbed.xcodeproj/project.pbxproj
@@ -0,0 +1,581 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 56;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66162B0EFA380010BFC8 /* AppDelegate.m */; };
+ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607A66212B0EFA390010BFC8 /* Assets.xcassets */; };
+ 607A66282B0EFA390010BFC8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66272B0EFA390010BFC8 /* main.m */; };
+ 607A66322B0EFA3A0010BFC8 /* visionOSTestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* visionOSTestbedTests.m */; };
+ 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */ = {isa = PBXBuildFile; fileRef = 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */; };
+ 608619542CB77BA900F46182 /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = 608619532CB77BA900F46182 /* app_packages */; };
+ 608619562CB7819B00F46182 /* app in Resources */ = {isa = PBXBuildFile; fileRef = 608619552CB7819B00F46182 /* app */; };
+ 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 */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 607A660A2B0EFA380010BFC8 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 607A66112B0EFA380010BFC8;
+ remoteInfo = iOSTestbed;
+ };
+/* 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 */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ EEB367CE2DADF5C900B9A1D7 /* Python.xcframework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = visionOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 607A66152B0EFA380010BFC8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
+ 607A66162B0EFA380010BFC8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
+ 607A66212B0EFA390010BFC8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 607A66272B0EFA390010BFC8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
+ 607A662D2B0EFA3A0010BFC8 /* visionOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = visionOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 607A66312B0EFA3A0010BFC8 /* visionOSTestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = visionOSTestbedTests.m; sourceTree = ""; };
+ 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "dylib-Info-template.plist"; sourceTree = ""; };
+ 607A66592B0F08600010BFC8 /* visionOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "visionOSTestbed-Info.plist"; sourceTree = ""; };
+ 608619532CB77BA900F46182 /* app_packages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app_packages; sourceTree = ""; };
+ 608619552CB7819B00F46182 /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = ""; };
+ EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 607A660F2B0EFA380010BFC8 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ EEE9C80D2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 607A662A2B0EFA3A0010BFC8 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ EEE9C80E2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 607A66092B0EFA380010BFC8 = {
+ isa = PBXGroup;
+ children = (
+ EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */,
+ 607A66142B0EFA380010BFC8 /* visionOSTestbed */,
+ 607A66302B0EFA3A0010BFC8 /* visionOSTestbedTests */,
+ 607A66132B0EFA380010BFC8 /* Products */,
+ 607A664F2B0EFFE00010BFC8 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 607A66132B0EFA380010BFC8 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */,
+ 607A662D2B0EFA3A0010BFC8 /* visionOSTestbedTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 607A66142B0EFA380010BFC8 /* visionOSTestbed */ = {
+ isa = PBXGroup;
+ children = (
+ 608619552CB7819B00F46182 /* app */,
+ 608619532CB77BA900F46182 /* app_packages */,
+ 607A66592B0F08600010BFC8 /* visionOSTestbed-Info.plist */,
+ 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */,
+ 607A66152B0EFA380010BFC8 /* AppDelegate.h */,
+ 607A66162B0EFA380010BFC8 /* AppDelegate.m */,
+ 607A66212B0EFA390010BFC8 /* Assets.xcassets */,
+ 607A66272B0EFA390010BFC8 /* main.m */,
+ );
+ path = visionOSTestbed;
+ sourceTree = "";
+ };
+ 607A66302B0EFA3A0010BFC8 /* visionOSTestbedTests */ = {
+ isa = PBXGroup;
+ children = (
+ 607A66312B0EFA3A0010BFC8 /* visionOSTestbedTests.m */,
+ );
+ path = visionOSTestbedTests;
+ sourceTree = "";
+ };
+ 607A664F2B0EFFE00010BFC8 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 607A66112B0EFA380010BFC8 /* visionOSTestbed */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */;
+ buildPhases = (
+ 607A660E2B0EFA380010BFC8 /* Sources */,
+ 607A660F2B0EFA380010BFC8 /* Frameworks */,
+ 607A66102B0EFA380010BFC8 /* Resources */,
+ 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */,
+ 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */,
+ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = visionOSTestbed;
+ productName = iOSTestbed;
+ productReference = 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 607A662C2B0EFA3A0010BFC8 /* visionOSTestbedTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbedTests" */;
+ buildPhases = (
+ 607A66292B0EFA3A0010BFC8 /* Sources */,
+ 607A662A2B0EFA3A0010BFC8 /* Frameworks */,
+ 607A662B2B0EFA3A0010BFC8 /* Resources */,
+ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */,
+ );
+ name = visionOSTestbedTests;
+ productName = iOSTestbedTests;
+ productReference = 607A662D2B0EFA3A0010BFC8 /* visionOSTestbedTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 607A660A2B0EFA380010BFC8 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastUpgradeCheck = 1500;
+ TargetAttributes = {
+ 607A66112B0EFA380010BFC8 = {
+ CreatedOnToolsVersion = 15.0.1;
+ };
+ 607A662C2B0EFA3A0010BFC8 = {
+ CreatedOnToolsVersion = 15.0.1;
+ TestTargetID = 607A66112B0EFA380010BFC8;
+ };
+ };
+ };
+ buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "visionOSTestbed" */;
+ compatibilityVersion = "Xcode 14.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 607A66092B0EFA380010BFC8;
+ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 607A66112B0EFA380010BFC8 /* visionOSTestbed */,
+ 607A662C2B0EFA3A0010BFC8 /* visionOSTestbedTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 607A66102B0EFA380010BFC8 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */,
+ 608619562CB7819B00F46182 /* app in Resources */,
+ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */,
+ 608619542CB77BA900F46182 /* app_packages in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 607A662B2B0EFA3A0010BFC8 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = "Install Target Specific Python Standard Library";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "set -e\n\nmkdir -p \"$CODESIGNING_FOLDER_PATH/python/lib\"\nif [ \"$EFFECTIVE_PLATFORM_NAME\" = \"-xrsimulator\" ]; then\n echo \"Installing Python modules for xrOS Simulator\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/xros-arm64-simulator/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nelse\n echo \"Installing Python modules for xrOS Device\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/xros-arm64/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nfi\n";
+ showEnvVarsInLog = 0;
+ };
+ 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = "Prepare Python Binary Modules";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "set -e\n\ninstall_dylib () {\n INSTALL_BASE=$1\n FULL_EXT=$2\n\n # The name of the extension file\n EXT=$(basename \"$FULL_EXT\")\n # The location of the extension file, relative to the bundle\n RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} \n # The path to the extension file, relative to the install base\n PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}\n # The full dotted name of the extension module, constructed from the file path.\n FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d \".\" -f 1 | tr \"/\" \".\"); \n # A bundle identifier; not actually used, but required by Xcode framework packaging\n FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr \"_\" \"-\")\n # The name of the framework folder.\n FRAMEWORK_FOLDER=\"Frameworks/$FULL_MODULE_NAME.framework\"\n\n # If the framework folder doesn't exist, create it.\n if [ ! -d \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\" ]; then\n echo \"Creating framework for $RELATIVE_EXT\" \n mkdir -p \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\"\n cp \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleExecutable -string \"$FULL_MODULE_NAME\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleIdentifier -string \"$FRAMEWORK_BUNDLE_ID\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n fi\n \n echo \"Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" \n mv \"$FULL_EXT\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\"\n # Create a placeholder .fwork file where the .so was\n echo \"$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" > ${FULL_EXT%.so}.fwork\n # Create a back reference to the .so file location in the framework\n echo \"${RELATIVE_EXT%.so}.fwork\" > \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin\" \n}\n\nPYTHON_VER=$(ls -1 \"$CODESIGNING_FOLDER_PATH/python/lib\")\necho \"Install Python $PYTHON_VER standard library extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib python/lib/$PYTHON_VER/lib-dynload/ \"$FULL_EXT\"\ndone\necho \"Install app package extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app_packages\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app_packages/ \"$FULL_EXT\"\ndone\necho \"Install app extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app/ \"$FULL_EXT\"\ndone\n\n# Clean up dylib template \nrm -f \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\"\necho \"Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)...\"\nfind \"$CODESIGNING_FOLDER_PATH/Frameworks\" -name \"*.framework\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der \"{}\" \\; \n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 607A660E2B0EFA380010BFC8 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */,
+ 607A66282B0EFA390010BFC8 /* main.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 607A66292B0EFA3A0010BFC8 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 607A66322B0EFA3A0010BFC8 /* visionOSTestbedTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 607A66112B0EFA380010BFC8 /* visionOSTestbed */;
+ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 607A663F2B0EFA3A0010BFC8 /* 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 = YES;
+ 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 = YES;
+ 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;
+ 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 = xros;
+ };
+ name = Debug;
+ };
+ 607A66402B0EFA3A0010BFC8 /* 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 = YES;
+ 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_USER_SCRIPT_SANDBOXING = YES;
+ 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;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = xros;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 607A66422B0EFA3A0010BFC8 /* 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";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ 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 = 7;
+ XROS_DEPLOYMENT_TARGET = 2.0;
+ };
+ name = Debug;
+ };
+ 607A66432B0EFA3A0010BFC8 /* 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";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ 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 = 7;
+ XROS_DEPLOYMENT_TARGET = 2.0;
+ };
+ name = Release;
+ };
+ 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.framework/Headers\"";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbedTests;
+ 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";
+ };
+ name = Debug;
+ };
+ 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.framework/Headers\"";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbedTests;
+ 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";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "visionOSTestbed" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 607A663F2B0EFA3A0010BFC8 /* Debug */,
+ 607A66402B0EFA3A0010BFC8 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 607A66422B0EFA3A0010BFC8 /* Debug */,
+ 607A66432B0EFA3A0010BFC8 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbedTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 607A66452B0EFA3A0010BFC8 /* Debug */,
+ 607A66462B0EFA3A0010BFC8 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 607A660A2B0EFA380010BFC8 /* Project object */;
+}
diff --git a/visionOS/testbed/visionOSTestbed/AppDelegate.h b/visionOS/testbed/visionOSTestbed/AppDelegate.h
new file mode 100644
index 00000000000000..8fe7ec8e64eeb2
--- /dev/null
+++ b/visionOS/testbed/visionOSTestbed/AppDelegate.h
@@ -0,0 +1,11 @@
+//
+// AppDelegate.h
+// visionOSTestbed
+//
+
+#import
+
+@interface AppDelegate : UIResponder
+
+
+@end
diff --git a/visionOS/testbed/visionOSTestbed/AppDelegate.m b/visionOS/testbed/visionOSTestbed/AppDelegate.m
new file mode 100644
index 00000000000000..b3ff14f7255984
--- /dev/null
+++ b/visionOS/testbed/visionOSTestbed/AppDelegate.m
@@ -0,0 +1,19 @@
+//
+// AppDelegate.m
+// visionOSTestbed
+//
+
+#import "AppDelegate.h"
+
+@interface AppDelegate ()
+
+@end
+
+@implementation AppDelegate
+
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ return YES;
+}
+
+@end
diff --git a/visionOS/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json b/visionOS/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 00000000000000..eb878970081645
--- /dev/null
+++ b/visionOS/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/visionOS/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json b/visionOS/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000000000..13613e3ee1a934
--- /dev/null
+++ b/visionOS/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/visionOS/testbed/visionOSTestbed/Assets.xcassets/Contents.json b/visionOS/testbed/visionOSTestbed/Assets.xcassets/Contents.json
new file mode 100644
index 00000000000000..73c00596a7fca3
--- /dev/null
+++ b/visionOS/testbed/visionOSTestbed/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/visionOS/testbed/visionOSTestbed/app/README b/visionOS/testbed/visionOSTestbed/app/README
new file mode 100644
index 00000000000000..af22c685f87976
--- /dev/null
+++ b/visionOS/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
+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/visionOS/testbed/visionOSTestbed/app_packages/README b/visionOS/testbed/visionOSTestbed/app_packages/README
new file mode 100644
index 00000000000000..42d7fdeb813250
--- /dev/null
+++ b/visionOS/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
+iOS Framework form.
+
+When the test suite runs, this folder will be on the PYTHONPATH.
diff --git a/visionOS/testbed/visionOSTestbed/dylib-Info-template.plist b/visionOS/testbed/visionOSTestbed/dylib-Info-template.plist
new file mode 100644
index 00000000000000..e91673b4fbcbc4
--- /dev/null
+++ b/visionOS/testbed/visionOSTestbed/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/visionOS/testbed/visionOSTestbed/main.m b/visionOS/testbed/visionOSTestbed/main.m
new file mode 100644
index 00000000000000..2bb491f25c851b
--- /dev/null
+++ b/visionOS/testbed/visionOSTestbed/main.m
@@ -0,0 +1,16 @@
+//
+// main.m
+// visionOSTestbed
+//
+
+#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/visionOS/testbed/visionOSTestbed/visionOSTestbed-Info.plist b/visionOS/testbed/visionOSTestbed/visionOSTestbed-Info.plist
new file mode 100644
index 00000000000000..fce9298555da00
--- /dev/null
+++ b/visionOS/testbed/visionOSTestbed/visionOSTestbed-Info.plist
@@ -0,0 +1,56 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDisplayName
+ ${PRODUCT_NAME}
+ CFBundleExecutable
+ ${EXECUTABLE_NAME}
+ CFBundleIdentifier
+ org.python.visionOSTestbed
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ ${PRODUCT_NAME}
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ TestArgs
+
+ test
+ -uall
+ --single-process
+ --rerun
+ -W
+
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+ UISceneConfigurations
+
+
+ UIRequiresFullScreen
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/visionOS/testbed/visionOSTestbedTests/visionOSTestbedTests.m b/visionOS/testbed/visionOSTestbedTests/visionOSTestbedTests.m
new file mode 100644
index 00000000000000..8f1cb020da2145
--- /dev/null
+++ b/visionOS/testbed/visionOSTestbedTests/visionOSTestbedTests.m
@@ -0,0 +1,162 @@
+#import
+#import
+
+@interface visionOSTestbedTests : XCTestCase
+
+@end
+
+@implementation visionOSTestbedTests
+
+
+- (void)testPython {
+ const char **argv;
+ int exit_code;
+ int failed;
+ PyStatus status;
+ PyPreConfig preconfig;
+ PyConfig config;
+ PyObject *sys_module;
+ PyObject *sys_path_attr;
+ NSArray *test_args;
+ NSString *python_home;
+ NSString *path;
+ wchar_t *wtmp_str;
+
+ NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
+
+ // Set some other common environment indicators to disable color, as the
+ // Xcode log can't display color. Stdout will report that it is *not* a
+ // TTY.
+ setenv("NO_COLOR", "1", true);
+ setenv("PYTHON_COLORS", "0", true);
+
+ // Arguments to pass into the test suite runner.
+ // argv[0] must identify the process; any subsequent arg
+ // will be handled as if it were an argument to `python -m test`
+ test_args = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"TestArgs"];
+ if (test_args == NULL) {
+ NSLog(@"Unable to identify test arguments.");
+ }
+ argv = malloc(sizeof(char *) * ([test_args count] + 1));
+ argv[0] = "visionOSTestbed";
+ for (int i = 1; i < [test_args count]; i++) {
+ argv[i] = [[test_args objectAtIndex:i] UTF8String];
+ }
+ NSLog(@"Test command: %@", test_args);
+
+ // Generate an isolated Python configuration.
+ NSLog(@"Configuring isolated Python...");
+ PyPreConfig_InitIsolatedConfig(&preconfig);
+ PyConfig_InitIsolatedConfig(&config);
+
+ // Configure the Python interpreter:
+ // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale.
+ // See https://docs.python.org/3/library/os.html#python-utf-8-mode.
+ preconfig.utf8_mode = 1;
+ // Use the system logger for stdout/err
+ config.use_system_logger = 1;
+ // Don't buffer stdio. We want output to appears in the log immediately
+ config.buffered_stdio = 0;
+ // Don't write bytecode; we can't modify the app bundle
+ // after it has been signed.
+ config.write_bytecode = 0;
+ // Ensure that signal handlers are installed
+ config.install_signal_handlers = 1;
+ // Run the test module.
+ config.run_module = Py_DecodeLocale([[test_args objectAtIndex:0] UTF8String], NULL);
+ // For debugging - enable verbose mode.
+ // config.verbose = 1;
+
+ NSLog(@"Pre-initializing Python runtime...");
+ status = Py_PreInitialize(&preconfig);
+ if (PyStatus_Exception(status)) {
+ XCTFail(@"Unable to pre-initialize Python interpreter: %s", status.err_msg);
+ PyConfig_Clear(&config);
+ return;
+ }
+
+ // Set the home for the Python interpreter
+ python_home = [NSString stringWithFormat:@"%@/python", resourcePath, nil];
+ NSLog(@"PythonHome: %@", python_home);
+ wtmp_str = Py_DecodeLocale([python_home UTF8String], NULL);
+ status = PyConfig_SetString(&config, &config.home, wtmp_str);
+ if (PyStatus_Exception(status)) {
+ XCTFail(@"Unable to set PYTHONHOME: %s", status.err_msg);
+ PyConfig_Clear(&config);
+ return;
+ }
+ PyMem_RawFree(wtmp_str);
+
+ // Read the site config
+ status = PyConfig_Read(&config);
+ if (PyStatus_Exception(status)) {
+ XCTFail(@"Unable to read site config: %s", status.err_msg);
+ PyConfig_Clear(&config);
+ return;
+ }
+
+ NSLog(@"Configure argc/argv...");
+ status = PyConfig_SetBytesArgv(&config, [test_args count], (char**) argv);
+ if (PyStatus_Exception(status)) {
+ XCTFail(@"Unable to configure argc/argv: %s", status.err_msg);
+ PyConfig_Clear(&config);
+ return;
+ }
+
+ NSLog(@"Initializing Python runtime...");
+ status = Py_InitializeFromConfig(&config);
+ if (PyStatus_Exception(status)) {
+ XCTFail(@"Unable to initialize Python interpreter: %s", status.err_msg);
+ PyConfig_Clear(&config);
+ return;
+ }
+
+ sys_module = PyImport_ImportModule("sys");
+ if (sys_module == NULL) {
+ XCTFail(@"Could not import sys module");
+ return;
+ }
+
+ sys_path_attr = PyObject_GetAttrString(sys_module, "path");
+ if (sys_path_attr == NULL) {
+ XCTFail(@"Could not access sys.path");
+ return;
+ }
+
+ // Add the app packages path
+ path = [NSString stringWithFormat:@"%@/app_packages", resourcePath, nil];
+ NSLog(@"App packages path: %@", path);
+ wtmp_str = Py_DecodeLocale([path UTF8String], NULL);
+ failed = PyList_Insert(sys_path_attr, 0, PyUnicode_FromString([path UTF8String]));
+ if (failed) {
+ XCTFail(@"Unable to add app packages to sys.path");
+ return;
+ }
+ PyMem_RawFree(wtmp_str);
+
+ path = [NSString stringWithFormat:@"%@/app", resourcePath, nil];
+ NSLog(@"App path: %@", path);
+ wtmp_str = Py_DecodeLocale([path UTF8String], NULL);
+ failed = PyList_Insert(sys_path_attr, 0, PyUnicode_FromString([path UTF8String]));
+ if (failed) {
+ XCTFail(@"Unable to add app to sys.path");
+ return;
+ }
+ PyMem_RawFree(wtmp_str);
+
+ // Ensure the working directory is the app folder.
+ chdir([path UTF8String]);
+
+ // Start the test suite. Print a separator to differentiate Python startup logs from app logs
+ NSLog(@"---------------------------------------------------------------------------");
+
+ exit_code = Py_RunMain();
+ XCTAssertEqual(exit_code, 0, @"Test suite did not pass");
+
+ NSLog(@"---------------------------------------------------------------------------");
+
+ Py_Finalize();
+}
+
+
+@end
diff --git a/watchOS/README.rst b/watchOS/README.rst
new file mode 100644
index 00000000000000..3522147845222f
--- /dev/null
+++ b/watchOS/README.rst
@@ -0,0 +1,108 @@
+========================
+Python on watchOS README
+========================
+
+:Authors:
+ Russell Keith-Magee (2023-11)
+
+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.
+
+watchOS 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 watchOS will look somethign like::
+
+ $ ./configure \
+ --enable-framework=/path/to/install \
+ --host=aarch64-apple-watchos \
+ --build=aarch64-apple-darwin \
+ --with-build-python=/path/to/python.exe
+ $ make
+ $ make install
+
+In this invocation:
+
+* ``/path/to/install`` is the location where the final Python.framework will be
+ output.
+
+* ``--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
+======================================
diff --git a/watchOS/Resources/Info.plist.in b/watchOS/Resources/Info.plist.in
new file mode 100644
index 00000000000000..e83ddfd2a43509
--- /dev/null
+++ b/watchOS/Resources/Info.plist.in
@@ -0,0 +1,34 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ Python
+ CFBundleGetInfoString
+ Python Runtime and Library
+ CFBundleIdentifier
+ @PYTHONFRAMEWORKIDENTIFIER@
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ Python
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ %VERSION%
+ CFBundleLongVersionString
+ %VERSION%, (c) 2001-2023 Python Software Foundation.
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ %VERSION%
+ CFBundleSupportedPlatforms
+
+ watchOS
+
+ MinimumOSVersion
+ @WATCHOS_DEPLOYMENT_TARGET@
+
+
diff --git a/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar b/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar
new file mode 100755
index 00000000000000..dda2b211bd5d5f
--- /dev/null
+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@"
diff --git a/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang b/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang
new file mode 100755
index 00000000000000..38c3de7f86b094
--- /dev/null
+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target arm64-apple-watchos-simulator "$@"
diff --git a/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++
new file mode 100755
index 00000000000000..e25acb1a52dae4
--- /dev/null
+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target arm64-apple-watchos-simulator "$@"
diff --git a/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp b/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp
new file mode 100755
index 00000000000000..0503ed40f64a5a
--- /dev/null
+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk watchsimulator clang -target arm64-apple-watchos-simulator -E "$@"
diff --git a/watchOS/Resources/bin/arm64_32-apple-watchos-ar b/watchOS/Resources/bin/arm64_32-apple-watchos-ar
new file mode 100755
index 00000000000000..029f9a320736ba
--- /dev/null
+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-ar
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk watchos${WATCHOS_SDK_VERSION} ar "$@"
diff --git a/watchOS/Resources/bin/arm64_32-apple-watchos-clang b/watchOS/Resources/bin/arm64_32-apple-watchos-clang
new file mode 100755
index 00000000000000..0c6a20795e9b76
--- /dev/null
+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-clang
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos "$@"
diff --git a/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ b/watchOS/Resources/bin/arm64_32-apple-watchos-clang++
new file mode 100755
index 00000000000000..89da49a1f7862d
--- /dev/null
+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-clang++
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang++ -target arm64_32-apple-watchos "$@"
diff --git a/watchOS/Resources/bin/arm64_32-apple-watchos-cpp b/watchOS/Resources/bin/arm64_32-apple-watchos-cpp
new file mode 100755
index 00000000000000..1b911273f08ce6
--- /dev/null
+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-cpp
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos -E "$@"
diff --git a/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar
new file mode 100755
index 00000000000000..dda2b211bd5d5f
--- /dev/null
+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@"
diff --git a/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang
new file mode 100755
index 00000000000000..185a8fb22e099e
--- /dev/null
+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos-simulator "$@"
diff --git a/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++
new file mode 100755
index 00000000000000..d1127720bb9b51
--- /dev/null
+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target x86_64-apple-watchos-simulator "$@"
diff --git a/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp
new file mode 100755
index 00000000000000..bd436d8a6c3917
--- /dev/null
+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos-simulator -E "$@"
diff --git a/watchOS/Resources/dylib-Info-template.plist b/watchOS/Resources/dylib-Info-template.plist
new file mode 100644
index 00000000000000..6f8c0bc2095955
--- /dev/null
+++ b/watchOS/Resources/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/watchOS/Resources/pyconfig.h b/watchOS/Resources/pyconfig.h
new file mode 100644
index 00000000000000..f842b987b2edba
--- /dev/null
+++ b/watchOS/Resources/pyconfig.h
@@ -0,0 +1,11 @@
+#ifdef __arm64__
+# ifdef __LP64__
+#include "pyconfig-arm64.h"
+# else
+#include "pyconfig-arm64_32.h"
+# endif
+#endif
+
+#ifdef __x86_64__
+#include "pyconfig-x86_64.h"
+#endif