From 3148e581fb0b4ff18c745c76fd2ab103add0a8d3 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 4 Jun 2025 02:57:45 +0200 Subject: [PATCH 1/4] [3.14] gh-135101: When choosing the default simulator device, don't use `simctl --set testing` (GH-135102) (#135113) On a fresh Xcode install (including some CI provider configurations), there is no pre-existing testing set that can be used to identify simulator models. Use the default device set to detect available models instead. Live testing simulators are still created in the testing set. (cherry picked from commit dba9de731b231ca0c079205f496d1e3d178b4fd3) Co-authored-by: Joe Rickerby --- iOS/testbed/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 6a5d3e94baf59ee2b73a4017d72e7e86d2fb68d2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 3 Feb 2025 09:11:28 +0800 Subject: [PATCH 2/4] Add support for tvOS and watchOS. --- .gitignore | 4 + Lib/platform.py | 75 ++++- Lib/sysconfig/__init__.py | 8 + Misc/platform_triplet.c | 20 ++ configure | 296 ++++++++++++++---- configure.ac | 232 +++++++++++--- iOS/Resources/Info.plist.in | 4 +- tvOS/README.rst | 108 +++++++ tvOS/Resources/Info.plist.in | 34 ++ tvOS/Resources/bin/arm64-apple-tvos-ar | 2 + tvOS/Resources/bin/arm64-apple-tvos-clang | 2 + tvOS/Resources/bin/arm64-apple-tvos-clang++ | 2 + tvOS/Resources/bin/arm64-apple-tvos-cpp | 2 + .../bin/arm64-apple-tvos-simulator-ar | 2 + .../bin/arm64-apple-tvos-simulator-clang | 2 + .../bin/arm64-apple-tvos-simulator-clang++ | 2 + .../bin/arm64-apple-tvos-simulator-cpp | 2 + .../bin/x86_64-apple-tvos-simulator-ar | 2 + .../bin/x86_64-apple-tvos-simulator-clang | 2 + .../bin/x86_64-apple-tvos-simulator-clang++ | 2 + .../bin/x86_64-apple-tvos-simulator-cpp | 2 + tvOS/Resources/dylib-Info-template.plist | 26 ++ tvOS/Resources/pyconfig.h | 7 + watchOS/README.rst | 108 +++++++ watchOS/Resources/Info.plist.in | 34 ++ .../bin/arm64-apple-watchos-simulator-ar | 2 + .../bin/arm64-apple-watchos-simulator-clang | 2 + .../bin/arm64-apple-watchos-simulator-clang++ | 2 + .../bin/arm64-apple-watchos-simulator-cpp | 2 + .../Resources/bin/arm64_32-apple-watchos-ar | 2 + .../bin/arm64_32-apple-watchos-clang | 2 + .../bin/arm64_32-apple-watchos-clang++ | 2 + .../Resources/bin/arm64_32-apple-watchos-cpp | 2 + .../bin/x86_64-apple-watchos-simulator-ar | 2 + .../bin/x86_64-apple-watchos-simulator-clang | 2 + .../x86_64-apple-watchos-simulator-clang++ | 2 + .../bin/x86_64-apple-watchos-simulator-cpp | 2 + watchOS/Resources/dylib-Info-template.plist | 26 ++ watchOS/Resources/pyconfig.h | 11 + 39 files changed, 920 insertions(+), 121 deletions(-) create mode 100644 tvOS/README.rst create mode 100644 tvOS/Resources/Info.plist.in create mode 100755 tvOS/Resources/bin/arm64-apple-tvos-ar create mode 100755 tvOS/Resources/bin/arm64-apple-tvos-clang create mode 100755 tvOS/Resources/bin/arm64-apple-tvos-clang++ create mode 100755 tvOS/Resources/bin/arm64-apple-tvos-cpp create mode 100755 tvOS/Resources/bin/arm64-apple-tvos-simulator-ar create mode 100755 tvOS/Resources/bin/arm64-apple-tvos-simulator-clang create mode 100755 tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ create mode 100755 tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp create mode 100755 tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar create mode 100755 tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang create mode 100755 tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ create mode 100755 tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp create mode 100644 tvOS/Resources/dylib-Info-template.plist create mode 100644 tvOS/Resources/pyconfig.h create mode 100644 watchOS/README.rst create mode 100644 watchOS/Resources/Info.plist.in create mode 100755 watchOS/Resources/bin/arm64-apple-watchos-simulator-ar create mode 100755 watchOS/Resources/bin/arm64-apple-watchos-simulator-clang create mode 100755 watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ create mode 100755 watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp create mode 100755 watchOS/Resources/bin/arm64_32-apple-watchos-ar create mode 100755 watchOS/Resources/bin/arm64_32-apple-watchos-clang create mode 100755 watchOS/Resources/bin/arm64_32-apple-watchos-clang++ create mode 100755 watchOS/Resources/bin/arm64_32-apple-watchos-cpp create mode 100755 watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar create mode 100755 watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang create mode 100755 watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ create mode 100755 watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp create mode 100644 watchOS/Resources/dylib-Info-template.plist create mode 100644 watchOS/Resources/pyconfig.h diff --git a/.gitignore b/.gitignore index 7965f6a404368e..ec0b026b08e7bf 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,10 @@ iOS/testbed/Python.xcframework/ios-*/Python.framework iOS/testbed/iOSTestbed.xcodeproj/project.xcworkspace iOS/testbed/iOSTestbed.xcodeproj/xcuserdata iOS/testbed/iOSTestbed.xcodeproj/xcshareddata +tvOS/Frameworks +tvOS/Resources/Info.plist +watchOS/Frameworks +watchOS/Resources/Info.plist Mac/Makefile Mac/PythonLauncher/Info.plist Mac/PythonLauncher/Makefile diff --git a/Lib/platform.py b/Lib/platform.py index 55e211212d4209..5728dfc193562a 100644 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -528,6 +528,54 @@ 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) + + 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 @@ -891,14 +939,25 @@ 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 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 from_subprocess(): """ Fall back to `uname -p` @@ -1058,9 +1117,13 @@ 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() vals = system, node, release, version, machine # Replace 'unknown' values with the more portable '' @@ -1350,6 +1413,10 @@ 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() else: macos_release = mac_ver()[0] if macos_release: diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index f93b98dd681536..1c345039c34376 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -730,6 +730,14 @@ 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 else: import _osx_support osname, release, machine = _osx_support.get_platform_osx( diff --git a/Misc/platform_triplet.c b/Misc/platform_triplet.c index f5cd73bdea8333..50f2f8988b792e 100644 --- a/Misc/platform_triplet.c +++ b/Misc/platform_triplet.c @@ -257,6 +257,26 @@ 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 // Older macOS SDKs do not define TARGET_OS_OSX # elif !defined(TARGET_OS_OSX) || TARGET_OS_OSX PLATFORM_TRIPLET=darwin diff --git a/configure b/configure index 884f8a4b0680d9..9a4cb5aeaf3b6d 100755 --- a/configure +++ b/configure @@ -982,6 +982,8 @@ LDFLAGS CFLAGS CC HAS_XCRUN +WATCHOS_DEPLOYMENT_TARGET +TVOS_DEPLOYMENT_TARGET IPHONEOS_DEPLOYMENT_TARGET EXPORT_MACOSX_DEPLOYMENT_TARGET CONFIGURE_MACOSX_DEPLOYMENT_TARGET @@ -4116,6 +4118,12 @@ then *-apple-ios*) ac_sys_system=iOS ;; + *-apple-tvos*) + ac_sys_system=tvOS + ;; + *-apple-watchos*) + ac_sys_system=watchOS + ;; *-*-darwin*) ac_sys_system=Darwin ;; @@ -4197,7 +4205,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, 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 +4220,14 @@ 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 ;; *) esac fi @@ -4220,6 +4236,14 @@ 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 ;; *) esac fi @@ -4228,6 +4252,14 @@ 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 ;; *) esac fi @@ -4236,6 +4268,14 @@ 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++ ;; *) esac fi @@ -4358,8 +4398,10 @@ 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\) ;; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 esac esac @@ -4368,6 +4410,8 @@ 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 ;; *) PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework @@ -4474,6 +4518,36 @@ 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" + ;; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 @@ -4485,6 +4559,8 @@ 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 ;; *) PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework @@ -4539,8 +4615,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) + # iOS/tvOS/watchOS 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 +4634,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) + # Always apply the compliance patch on iOS/tvOS/watchOS; 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; } @@ -4614,6 +4690,50 @@ 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 + ;; *-*-darwin*) case "$host_cpu" in arm*) @@ -4704,9 +4824,13 @@ 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, defining _POSIX_C_SOURCE also disables platform specific features. iOS/*) define_xopen_source=no;; + tvOS/*) + define_xopen_source=no;; + watchOS/*) + 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 +4893,10 @@ 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 enforced by the selected host triple. + + # checks for alternative programs @@ -4810,6 +4937,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 +7316,10 @@ case $ac_sys_system in #( MULTIARCH="" ;; #( iOS) : MULTIARCH="" ;; #( + tvOS) : + MULTIARCH="" ;; #( + watchOS) : + MULTIARCH="" ;; #( FreeBSD*) : MULTIARCH="" ;; #( *) : @@ -7199,7 +7340,7 @@ fi printf "%s\n" "$MULTIARCH" >&6; } case $ac_sys_system in #( - iOS) : + iOS|tvOS|watchOS) : SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #( *) : SOABI_PLATFORM=$PLATFORM_TRIPLET @@ -7250,6 +7391,14 @@ 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-*-linux-android/clang) : PY_SUPPORT_TIER=3 ;; #( x86_64-*-linux-android/clang) : @@ -7686,7 +7835,7 @@ then case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; - iOS) + iOS|tvOS|watchOS) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; @@ -7752,7 +7901,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) LDLIBRARY='libpython$(LDVERSION).dylib' ;; AIX*) @@ -13574,7 +13723,7 @@ then BLDSHARED="$LDSHARED" fi ;; - iOS/*) + iOS/*|tvOS/*|watchOS/*) LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' BLDSHARED="$LDSHARED" @@ -13707,7 +13856,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/*) LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too @@ -13731,7 +13880,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"; then LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' fi ;; @@ -15508,7 +15657,7 @@ then : ctypes_malloc_closure=yes ;; #( - iOS) : + iOS|tvOS|watchOS) : ctypes_malloc_closure=yes ;; #( @@ -19260,12 +19409,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 +19469,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 +19895,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 +20213,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 +20481,11 @@ fi fi -# iOS defines some system methods that can be linked (so they are +# iOS/tvOS/watchOS 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" ; then ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy" if test "x$ac_cv_func_getentropy" = xyes then : @@ -20400,6 +20507,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 +23998,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 +24113,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 +24296,7 @@ esac fi done +fi # check for long file support functions ac_fn_c_check_func "$LINENO" "fseek64" "ac_cv_func_fseek64" @@ -24406,10 +24562,10 @@ fi done -# On Android and iOS, clock_settime can be linked (so it is found by +# On Android, iOS, tvOS and watchOS, 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" then for ac_func in clock_settime @@ -26748,8 +26904,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"; then MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" fi @@ -29619,7 +29775,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" ; then ac_cv_file__dev_ptmx=no ac_cv_file__dev_ptc=no else @@ -30129,7 +30285,7 @@ else case e in #( with_ensurepip=no ;; #( WASI) : with_ensurepip=no ;; #( - iOS) : + iOS|tvOS|watchOS) : with_ensurepip=no ;; #( *) : with_ensurepip=upgrade @@ -31110,7 +31266,7 @@ case $ac_sys_system in #( ;; #( Darwin) : ;; #( - iOS) : + iOS|tvOS|watchOS) : @@ -35272,6 +35428,8 @@ 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" ;; "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..f904e22e65eb53 100644 --- a/configure.ac +++ b/configure.ac @@ -330,6 +330,12 @@ then *-apple-ios*) ac_sys_system=iOS ;; + *-apple-tvos*) + ac_sys_system=tvOS + ;; + *-apple-watchos*) + ac_sys_system=watchOS + ;; *-*-darwin*) ac_sys_system=Darwin ;; @@ -405,7 +411,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, 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 +426,14 @@ 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 ;; *) esac fi @@ -428,6 +442,14 @@ 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 ;; *) esac fi @@ -436,6 +458,14 @@ 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 ;; *) esac fi @@ -444,6 +474,14 @@ 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++ ;; *) esac fi @@ -558,8 +596,10 @@ 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\) ;; *) AC_MSG_ERROR([Unknown platform for framework build]) esac esac @@ -568,6 +608,8 @@ 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]) ;; *) PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework @@ -670,6 +712,34 @@ 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]) + ;; *) AC_MSG_ERROR([Unknown platform for framework build]) ;; @@ -678,6 +748,8 @@ 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]) ;; *) PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework @@ -730,8 +802,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) + # iOS/tvOS/watchOS 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 +817,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) + # Always apply the compliance patch on iOS/tvOS/watchOS; 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]) ;; @@ -794,6 +866,46 @@ 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 + ;; *-*-darwin*) case "$host_cpu" in arm*) @@ -883,9 +995,13 @@ 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, defining _POSIX_C_SOURCE also disables platform specific features. iOS/*) define_xopen_source=no;; + tvOS/*) + define_xopen_source=no;; + watchOS/*) + 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 +1060,11 @@ 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 enforced by the selected host triple. AC_SUBST([IPHONEOS_DEPLOYMENT_TARGET]) +AC_SUBST([TVOS_DEPLOYMENT_TARGET]) +AC_SUBST([WATCHOS_DEPLOYMENT_TARGET]) # checks for alternative programs @@ -979,11 +1098,17 @@ 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 version. 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 +1297,8 @@ AC_MSG_CHECKING([for multiarch]) AS_CASE([$ac_sys_system], [Darwin*], [MULTIARCH=""], [iOS], [MULTIARCH=""], + [tvOS], [MULTIARCH=""], + [watchOS], [MULTIARCH=""], [FreeBSD*], [MULTIARCH=""], [MULTIARCH=$($CC --print-multiarch 2>/dev/null)] ) @@ -1193,7 +1320,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], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`], [SOABI_PLATFORM=$PLATFORM_TRIPLET] ) @@ -1227,6 +1354,10 @@ 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-*-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 +1667,7 @@ then case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; - iOS) + iOS|tvOS|watchOS) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) AC_MSG_ERROR([Unknown platform for framework build]);; @@ -1601,7 +1732,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) LDLIBRARY='libpython$(LDVERSION).dylib' ;; AIX*) @@ -3470,7 +3601,7 @@ then BLDSHARED="$LDSHARED" fi ;; - iOS/*) + iOS/*|tvOS/*|watchOS/*) LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' BLDSHARED="$LDSHARED" @@ -3594,7 +3725,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/*) LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too @@ -3618,7 +3749,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"; then LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' fi ;; @@ -4106,7 +4237,7 @@ AS_VAR_IF([have_libffi], [yes], [ dnl when do we need USING_APPLE_OS_LIBFFI? ctypes_malloc_closure=yes ], - [iOS], [ + [iOS|tvOS|watchOS], [ ctypes_malloc_closure=yes ], [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])] @@ -5215,9 +5346,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 +5356,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 +5366,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 +5381,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 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" ; 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 +5677,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 +5731,10 @@ AC_CHECK_FUNCS([clock_getres], [], [ ]) ]) -# On Android and iOS, clock_settime can be linked (so it is found by +# On Android, iOS, tvOS and watchOS, 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" then AC_CHECK_FUNCS([clock_settime], [], [ AC_CHECK_LIB([rt], [clock_settime], [ @@ -6345,8 +6485,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"; then MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" fi @@ -7005,7 +7145,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" ; then ac_cv_file__dev_ptmx=no ac_cv_file__dev_ptc=no else @@ -7307,7 +7447,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], [with_ensurepip=no], [with_ensurepip=upgrade] ) ]) @@ -7719,7 +7859,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], [ 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/tvOS/README.rst b/tvOS/README.rst new file mode 100644 index 00000000000000..1f793252caf627 --- /dev/null +++ b/tvOS/README.rst @@ -0,0 +1,108 @@ +===================== +Python on tvOS README +===================== + +:Authors: + Russell Keith-Magee (2023-11) + +This document provides a quick overview of some tvOS specific features in the +Python distribution. + +Compilers for building on tvOS +============================== + +Building for tvOS requires the use of Apple's Xcode tooling. It is strongly +recommended that you use the most recent stable release of Xcode, on the +most recently released macOS. + +tvOS specific arguments to configure +=================================== + +* ``--enable-framework[=DIR]`` + + This argument specifies the location where the Python.framework will + be installed. + +* ``--with-framework-name=NAME`` + + Specify the name for the python framework, defaults to ``Python``. + + +Building and using Python on tvOS +================================= + +ABIs and Architectures +---------------------- + +tvOS apps can be deployed on physical devices, and on the tvOS simulator. +Although the API used on these devices is identical, the ABI is different - you +need to link against different libraries for an tvOS device build +(``appletvos``) or an tvOS simulator build (``appletvsimulator``). Apple uses +the XCframework format to allow specifying a single dependency that supports +multiple ABIs. An XCframework is a wrapper around multiple ABI-specific +frameworks. + +tvOS can also support different CPU architectures within each ABI. At present, +there is only a single support ed architecture on physical devices - ARM64. +However, the *simulator* supports 2 architectures - ARM64 (for running on Apple +Silicon machines), and x86_64 (for running on older Intel-based machines.) + +To support multiple CPU architectures on a single platform, Apple uses a "fat +binary" format - a single physical file that contains support for multiple +architectures. + +How do I build Python for tvOS? +------------------------------- + +The Python build system will build a ``Python.framework`` that supports a +*single* ABI with a *single* architecture. If you want to use Python in an tvOS +project, you need to: + +1. Produce multiple ``Python.framework`` builds, one for each ABI and architecture; +2. Merge the binaries for each architecture on a given ABI into a single "fat" binary; +3. Merge the "fat" frameworks for each ABI into a single XCframework. + +tvOS builds of Python *must* be constructed as framework builds. To support this, +you must provide the ``--enable-framework`` flag when configuring the build. + +The build also requires the use of cross-compilation. The commands for building +Python for tvOS will look somethign like:: + + $ ./configure \ + --enable-framework=/path/to/install \ + --host=aarch64-apple-tvos \ + --build=aarch64-apple-darwin \ + --with-build-python=/path/to/python.exe + $ make + $ make install + +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/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 From 68b83a6ed8cff2740610c415c1a15bd90677a709 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 23 Apr 2025 14:33:51 +0800 Subject: [PATCH 3/4] Add support for visionOS. --- .gitignore | 10 + Lib/ctypes/__init__.py | 2 +- Lib/ctypes/util.py | 4 +- Lib/importlib/_bootstrap_external.py | 4 +- Lib/platform.py | 37 +- Lib/site.py | 4 +- Lib/subprocess.py | 2 +- Lib/sysconfig/__init__.py | 9 +- Lib/test/datetimetester.py | 4 +- Lib/test/support/__init__.py | 4 +- Lib/test/test_ctypes/test_dllist.py | 2 +- Lib/test/test_platform.py | 14 +- Lib/test/test_webbrowser.py | 3 +- Lib/webbrowser.py | 8 +- Makefile.pre.in | 44 +- Misc/platform_triplet.c | 6 + config.sub | 4 +- configure | 125 +++- configure.ac | 118 +++- visionOS/Resources/Info.plist.in | 34 + visionOS/Resources/bin/arm64-apple-xros-ar | 2 + visionOS/Resources/bin/arm64-apple-xros-clang | 2 + .../Resources/bin/arm64-apple-xros-clang++ | 2 + visionOS/Resources/bin/arm64-apple-xros-cpp | 2 + .../bin/arm64-apple-xros-simulator-ar | 2 + .../bin/arm64-apple-xros-simulator-clang | 2 + .../bin/arm64-apple-xros-simulator-clang++ | 2 + .../bin/arm64-apple-xros-simulator-cpp | 2 + visionOS/Resources/dylib-Info-template.plist | 30 + .../testbed/Python.xcframework/Info.plist | 43 ++ .../xros-arm64-simulator/README | 4 + .../Python.xcframework/xros-arm64/README | 4 + visionOS/testbed/__main__.py | 512 +++++++++++++++ .../visionOSTestbed.xcodeproj/project.pbxproj | 581 ++++++++++++++++++ .../testbed/visionOSTestbed/AppDelegate.h | 11 + .../testbed/visionOSTestbed/AppDelegate.m | 19 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../Assets.xcassets/Contents.json | 6 + visionOS/testbed/visionOSTestbed/app/README | 7 + .../visionOSTestbed/app_packages/README | 7 + .../visionOSTestbed/dylib-Info-template.plist | 30 + visionOS/testbed/visionOSTestbed/main.m | 16 + .../visionOSTestbed-Info.plist | 56 ++ .../visionOSTestbedTests.m | 162 +++++ 45 files changed, 1888 insertions(+), 78 deletions(-) create mode 100644 visionOS/Resources/Info.plist.in create mode 100755 visionOS/Resources/bin/arm64-apple-xros-ar create mode 100755 visionOS/Resources/bin/arm64-apple-xros-clang create mode 100755 visionOS/Resources/bin/arm64-apple-xros-clang++ create mode 100755 visionOS/Resources/bin/arm64-apple-xros-cpp create mode 100755 visionOS/Resources/bin/arm64-apple-xros-simulator-ar create mode 100755 visionOS/Resources/bin/arm64-apple-xros-simulator-clang create mode 100755 visionOS/Resources/bin/arm64-apple-xros-simulator-clang++ create mode 100755 visionOS/Resources/bin/arm64-apple-xros-simulator-cpp create mode 100644 visionOS/Resources/dylib-Info-template.plist create mode 100644 visionOS/testbed/Python.xcframework/Info.plist create mode 100644 visionOS/testbed/Python.xcframework/xros-arm64-simulator/README create mode 100644 visionOS/testbed/Python.xcframework/xros-arm64/README create mode 100644 visionOS/testbed/__main__.py create mode 100644 visionOS/testbed/visionOSTestbed.xcodeproj/project.pbxproj create mode 100644 visionOS/testbed/visionOSTestbed/AppDelegate.h create mode 100644 visionOS/testbed/visionOSTestbed/AppDelegate.m create mode 100644 visionOS/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 visionOS/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 visionOS/testbed/visionOSTestbed/Assets.xcassets/Contents.json create mode 100644 visionOS/testbed/visionOSTestbed/app/README create mode 100644 visionOS/testbed/visionOSTestbed/app_packages/README create mode 100644 visionOS/testbed/visionOSTestbed/dylib-Info-template.plist create mode 100644 visionOS/testbed/visionOSTestbed/main.m create mode 100644 visionOS/testbed/visionOSTestbed/visionOSTestbed-Info.plist create mode 100644 visionOS/testbed/visionOSTestbedTests/visionOSTestbedTests.m diff --git a/.gitignore b/.gitignore index ec0b026b08e7bf..3893a91e77198a 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,7 @@ Lib/test/data/* /Makefile /Makefile.pre /iOSTestbed.* +/visionOSTestbed.* iOS/Frameworks/ iOS/Resources/Info.plist iOS/testbed/build @@ -81,10 +82,19 @@ iOS/testbed/Python.xcframework/ios-*/Python.framework iOS/testbed/iOSTestbed.xcodeproj/project.xcworkspace iOS/testbed/iOSTestbed.xcodeproj/xcuserdata iOS/testbed/iOSTestbed.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 5728dfc193562a..cad919bc0c4969 100644 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -576,6 +576,30 @@ def watchos_ver(system="", release="", model="", is_simulator=False): 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 @@ -775,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 @@ -939,7 +963,7 @@ def get_OpenVMS(): csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) return 'Alpha' if cpu_number >= 128 else 'VAX' - # On the iOS/tvOS/watchOS simulator, os.uname returns the architecture as + # 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. @@ -958,6 +982,11 @@ def get_watchos(): 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` @@ -1124,6 +1153,8 @@ def uname(): 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 '' @@ -1417,6 +1448,8 @@ def platform(aliased=False, terse=False): 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 1c345039c34376..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): @@ -738,6 +741,10 @@ def get_platform(): 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..e436b2efb8e205 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,41 @@ 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)" run --verbose -- test -uall --single-process --rerun -W + $(PYTHON_FOR_BUILD) "$(XCFOLDER-visionOS)" 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 50f2f8988b792e..6c1863c943b353 100644 --- a/Misc/platform_triplet.c +++ b/Misc/platform_triplet.c @@ -277,6 +277,12 @@ PLATFORM_TRIPLET=arm64-watchsimulator # 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 9a4cb5aeaf3b6d..7c93d36e71729b 100755 --- a/configure +++ b/configure @@ -982,6 +982,8 @@ LDFLAGS CFLAGS CC HAS_XCRUN +EXPORT_XROS_DEPLOYMENT_TARGET +XROS_DEPLOYMENT_TARGET WATCHOS_DEPLOYMENT_TARGET TVOS_DEPLOYMENT_TARGET IPHONEOS_DEPLOYMENT_TARGET @@ -4124,6 +4126,9 @@ then *-apple-watchos*) ac_sys_system=watchOS ;; + *-apple-xros*) + ac_sys_system=visionOS + ;; *-*-darwin*) ac_sys_system=Darwin ;; @@ -4205,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/tvOS/watchOS, 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 @@ -4228,6 +4233,9 @@ if test -z "$AR"; then 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 @@ -4244,6 +4252,9 @@ if test -z "$CC"; then 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 @@ -4260,6 +4271,9 @@ if test -z "$CPP"; then 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 @@ -4276,6 +4290,9 @@ if test -z "$CXX"; then 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 @@ -4402,6 +4419,7 @@ then : 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 @@ -4412,6 +4430,7 @@ then : 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 @@ -4548,6 +4567,21 @@ then : 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 @@ -4561,6 +4595,7 @@ else case e 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 @@ -4615,8 +4650,8 @@ then : case "$withval" in yes) case $ac_sys_system in - Darwin|iOS|tvOS|watchOS) - # iOS/tvOS/watchOS 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 ;; @@ -4634,8 +4669,8 @@ printf "%s\n" "applying custom app store compliance patch" >&6; } else case e in #( e) case $ac_sys_system in - iOS|tvOS|watchOS) - # Always apply the compliance patch on iOS/tvOS/watchOS; 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; } @@ -4653,6 +4688,8 @@ fi +EXPORT_XROS_DEPLOYMENT_TARGET='#' + if test "$cross_compiling" = yes; then case "$host" in @@ -4734,6 +4771,34 @@ printf "%s\n" "$WATCHOS_DEPLOYMENT_TARGET" >&6; } ;; 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*) @@ -4824,13 +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/tvOS/watchOS, 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) @@ -4894,10 +4961,13 @@ CONFIGURE_MACOSX_DEPLOYMENT_TARGET= EXPORT_MACOSX_DEPLOYMENT_TARGET='#' # Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET / -# WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple. +# WATCHOS_DEPLOYMENT_TARGET / XROS_DEPLOYMENT_TARGET enforced by the selected host triple. + +# XROS_DEPLOYMENT_TARGET should get exported + # checks for alternative programs @@ -7320,6 +7390,8 @@ case $ac_sys_system in #( MULTIARCH="" ;; #( watchOS) : MULTIARCH="" ;; #( + visionOS) : + MULTIARCH="" ;; #( FreeBSD*) : MULTIARCH="" ;; #( *) : @@ -7340,7 +7412,7 @@ fi printf "%s\n" "$MULTIARCH" >&6; } case $ac_sys_system in #( - iOS|tvOS|watchOS) : + iOS|tvOS|watchOS|visionOS) : SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #( *) : SOABI_PLATFORM=$PLATFORM_TRIPLET @@ -7399,6 +7471,10 @@ case $host/$ac_cv_cc_name in #( 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) : @@ -7835,7 +7911,7 @@ then case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; - iOS|tvOS|watchOS) + iOS|tvOS|watchOS|visionOS) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; @@ -7901,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|tvOS|watchOS) + iOS|tvOS|watchOS|visionOS) LDLIBRARY='libpython$(LDVERSION).dylib' ;; AIX*) @@ -13723,7 +13799,7 @@ then BLDSHARED="$LDSHARED" fi ;; - iOS/*|tvOS/*|watchOS/*) + iOS/*|tvOS/*|watchOS/*|visionOS/*) LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' BLDSHARED="$LDSHARED" @@ -13856,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/*|tvOS/*|watchOS/*) + Darwin/*|iOS/*|tvOS/*|watchOS/*|visionOS/*) LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too @@ -13880,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" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; 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 ;; @@ -15657,7 +15733,7 @@ then : ctypes_malloc_closure=yes ;; #( - iOS|tvOS|watchOS) : + iOS|tvOS|watchOS|visionOS) : ctypes_malloc_closure=yes ;; #( @@ -20481,11 +20557,11 @@ fi fi -# iOS/tvOS/watchOS define 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" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; 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 : @@ -24562,10 +24638,10 @@ fi done -# On Android, iOS, tvOS and watchOS, 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" -a "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" +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 @@ -24882,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" @@ -26905,7 +26981,7 @@ if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MA fi # 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"; then +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 @@ -29775,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" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" ; 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 @@ -30285,7 +30361,7 @@ else case e in #( with_ensurepip=no ;; #( WASI) : with_ensurepip=no ;; #( - iOS|tvOS|watchOS) : + iOS|tvOS|watchOS|visionOS) : with_ensurepip=no ;; #( *) : with_ensurepip=upgrade @@ -31234,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=;; @@ -31266,7 +31342,7 @@ case $ac_sys_system in #( ;; #( Darwin) : ;; #( - iOS|tvOS|watchOS) : + iOS|tvOS|watchOS|visionOS) : @@ -35430,6 +35506,7 @@ do "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 f904e22e65eb53..7ab0609bf8ab3e 100644 --- a/configure.ac +++ b/configure.ac @@ -336,6 +336,9 @@ then *-apple-watchos*) ac_sys_system=watchOS ;; + *-apple-xros*) + ac_sys_system=visionOS + ;; *-*-darwin*) ac_sys_system=Darwin ;; @@ -411,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/tvOS/watchOS, 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 @@ -434,6 +437,9 @@ if test -z "$AR"; then 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 @@ -450,6 +456,9 @@ if test -z "$CC"; then 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 @@ -466,6 +475,9 @@ if test -z "$CPP"; then 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 @@ -482,6 +494,9 @@ if test -z "$CXX"; then 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 @@ -600,6 +615,7 @@ AC_ARG_ENABLE([framework], 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 @@ -610,6 +626,7 @@ AC_ARG_ENABLE([framework], 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 @@ -740,6 +757,20 @@ AC_ARG_ENABLE([framework], 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]) ;; @@ -750,6 +781,7 @@ AC_ARG_ENABLE([framework], 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 @@ -802,8 +834,8 @@ AC_ARG_WITH( case "$withval" in yes) case $ac_sys_system in - Darwin|iOS|tvOS|watchOS) - # iOS/tvOS/watchOS 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]) ;; @@ -817,8 +849,8 @@ AC_ARG_WITH( esac ],[ case $ac_sys_system in - iOS|tvOS|watchOS) - # Always apply the compliance patch on iOS/tvOS/watchOS; 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]) ;; @@ -831,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 @@ -906,6 +940,30 @@ if test "$cross_compiling" = yes; then ;; 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*) @@ -995,13 +1053,15 @@ case $ac_sys_system/$ac_sys_release in define_xopen_source=no;; Darwin/@<:@[12]@:>@@<:@0-9@:>@.*) define_xopen_source=no;; - # On iOS/tvOS/watchOS, 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) @@ -1061,10 +1121,13 @@ CONFIGURE_MACOSX_DEPLOYMENT_TARGET= EXPORT_MACOSX_DEPLOYMENT_TARGET='#' # Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET / -# WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple. +# 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 @@ -1098,7 +1161,9 @@ AS_CASE([$host], ], ) -dnl Add the compiler flag for the iOS/tvOS/watchOS 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}"]) @@ -1299,6 +1364,7 @@ AS_CASE([$ac_sys_system], [iOS], [MULTIARCH=""], [tvOS], [MULTIARCH=""], [watchOS], [MULTIARCH=""], + [visionOS], [MULTIARCH=""], [FreeBSD*], [MULTIARCH=""], [MULTIARCH=$($CC --print-multiarch 2>/dev/null)] ) @@ -1320,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|tvOS|watchOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`], + [iOS|tvOS|watchOS|visionOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`], [SOABI_PLATFORM=$PLATFORM_TRIPLET] ) @@ -1358,6 +1424,8 @@ AS_CASE([$host/$ac_cv_cc_name], [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 @@ -1667,7 +1735,7 @@ then case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; - iOS|tvOS|watchOS) + iOS|tvOS|watchOS|visionOS) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) AC_MSG_ERROR([Unknown platform for framework build]);; @@ -1732,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|tvOS|watchOS) + iOS|tvOS|watchOS|visionOS) LDLIBRARY='libpython$(LDVERSION).dylib' ;; AIX*) @@ -3601,7 +3669,7 @@ then BLDSHARED="$LDSHARED" fi ;; - iOS/*|tvOS/*|watchOS/*) + iOS/*|tvOS/*|watchOS/*|visionOS/*) LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' BLDSHARED="$LDSHARED" @@ -3725,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/*|tvOS/*|watchOS/*) + Darwin/*|iOS/*|tvOS/*|watchOS/*|visionOS/*) LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too @@ -3749,7 +3817,7 @@ then LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' fi LINKFORSHARED="$LINKFORSHARED" - elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; 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 ;; @@ -4237,7 +4305,7 @@ AS_VAR_IF([have_libffi], [yes], [ dnl when do we need USING_APPLE_OS_LIBFFI? ctypes_malloc_closure=yes ], - [iOS|tvOS|watchOS], [ + [iOS|tvOS|watchOS|visionOS], [ ctypes_malloc_closure=yes ], [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])] @@ -5381,11 +5449,11 @@ if test "$MACHDEP" != linux; then AC_CHECK_FUNCS([lchmod]) fi -# iOS/tvOS/watchOS define 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" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then +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 @@ -5731,10 +5799,10 @@ AC_CHECK_FUNCS([clock_getres], [], [ ]) ]) -# On Android, iOS, tvOS and watchOS, 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" -a "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" +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], [ @@ -5892,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" @@ -6486,7 +6554,7 @@ if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MA fi # 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"; then +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 @@ -7145,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" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" ; 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 @@ -7447,7 +7515,7 @@ AC_ARG_WITH([ensurepip], AS_CASE([$ac_sys_system], [Emscripten], [with_ensurepip=no], [WASI], [with_ensurepip=no], - [iOS|tvOS|watchOS], [with_ensurepip=no], + [iOS|tvOS|watchOS|visionOS], [with_ensurepip=no], [with_ensurepip=upgrade] ) ]) @@ -7834,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=;; @@ -7859,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|tvOS|watchOS], [ + [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/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 From f7aadf7da6be2839eb6264dec07514d5e3e7dc66 Mon Sep 17 00:00:00 2001 From: John Zhou Date: Tue, 3 Jun 2025 15:56:10 -0500 Subject: [PATCH 4/4] Add testbed for tvOS --- .gitignore | 9 + Makefile.pre.in | 30 + tvOS/README.rst | 3 +- tvOS/testbed/Python.xcframework/Info.plist | 44 ++ .../Python.xcframework/tvos-arm64/README | 5 + .../tvos-arm64_x86_64-simulator/README | 5 + tvOS/testbed/__main__.py | 512 +++++++++++++++++ tvOS/testbed/app/README | 7 + tvOS/testbed/app_packages/README | 7 + .../tvOSTestbed.xcodeproj/project.pbxproj | 514 ++++++++++++++++++ tvOS/testbed/tvOSTestbed/AppDelegate.h | 11 + tvOS/testbed/tvOSTestbed/AppDelegate.m | 19 + .../Base.lproj/LaunchScreen.storyboard | 24 + .../tvOSTestbed/dylib-Info-template.plist | 26 + tvOS/testbed/tvOSTestbed/main.m | 16 + .../tvOSTestbed/tvOSTestbed-Info.plist | 64 +++ .../tvOSTestbedTests/tvOSTestbedTests.m | 162 ++++++ 17 files changed, 1457 insertions(+), 1 deletion(-) create mode 100644 tvOS/testbed/Python.xcframework/Info.plist create mode 100644 tvOS/testbed/Python.xcframework/tvos-arm64/README create mode 100644 tvOS/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README create mode 100644 tvOS/testbed/__main__.py create mode 100644 tvOS/testbed/app/README create mode 100644 tvOS/testbed/app_packages/README create mode 100644 tvOS/testbed/tvOSTestbed.xcodeproj/project.pbxproj create mode 100644 tvOS/testbed/tvOSTestbed/AppDelegate.h create mode 100644 tvOS/testbed/tvOSTestbed/AppDelegate.m create mode 100644 tvOS/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard create mode 100644 tvOS/testbed/tvOSTestbed/dylib-Info-template.plist create mode 100644 tvOS/testbed/tvOSTestbed/main.m create mode 100644 tvOS/testbed/tvOSTestbed/tvOSTestbed-Info.plist create mode 100644 tvOS/testbed/tvOSTestbedTests/tvOSTestbedTests.m diff --git a/.gitignore b/.gitignore index 3893a91e77198a..b0243685138bac 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,7 @@ Lib/test/data/* /Makefile /Makefile.pre /iOSTestbed.* +/tvOSTestbed.* /visionOSTestbed.* iOS/Frameworks/ iOS/Resources/Info.plist @@ -82,6 +83,14 @@ 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 diff --git a/Makefile.pre.in b/Makefile.pre.in index e436b2efb8e205..651320d7164de5 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2326,6 +2326,36 @@ testvisionos: # 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-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 # information about the build environment. diff --git a/tvOS/README.rst b/tvOS/README.rst index 1f793252caf627..57d1f3632e2cbd 100644 --- a/tvOS/README.rst +++ b/tvOS/README.rst @@ -66,8 +66,9 @@ tvOS builds of Python *must* be constructed as framework builds. To support this you must provide the ``--enable-framework`` flag when configuring the build. The build also requires the use of cross-compilation. The commands for building -Python for tvOS will look somethign like:: +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 \ 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