Skip to content

Commit 0b8aaa9

Browse files
committed
Add support for tvOS, visionOS and watchOS.
1 parent 3dc70ba commit 0b8aaa9

104 files changed

Lines changed: 3155 additions & 192 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ Apple/testbed/Python.xcframework/*/lib
8080
Apple/testbed/Python.xcframework/*/Python.framework
8181
Apple/testbed/*Testbed.xcodeproj/project.xcworkspace
8282
Apple/testbed/*Testbed.xcodeproj/xcuserdata
83+
Apple/tvOS/Frameworks
84+
Apple/tvOS/Resources/Info.plist
85+
Apple/visionOS/Frameworks
86+
Apple/visionOS/Resources/Info.plist
87+
Apple/watchOS/Frameworks
88+
Apple/watchOS/Resources/Info.plist
8389
Mac/Makefile
8490
Mac/PythonLauncher/Info.plist
8591
Mac/PythonLauncher/Makefile

Apple/__main__.py

Lines changed: 78 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@
55
# This script simplifies the process of configuring, compiling and packaging an
66
# XCframework for an Apple platform.
77
#
8-
# At present, it only supports iOS, but it has been constructed so that it
9-
# could be used on any Apple platform.
8+
# At present, it supports iOS, tvOS, visionOS and watchOS, but it has been
9+
# constructed so that it could be used on any Apple platform.
1010
#
1111
# The simplest entry point is:
1212
#
1313
# $ python Apple ci iOS
1414
#
15+
# (replace iOS with tvOS, visionOS or watchOS as required.)
16+
#
1517
# which will:
1618
# * Clean any pre-existing build artefacts
1719
# * Configure and make a Python that can be used for the build
18-
# * Configure and make a Python for each supported iOS architecture and ABI
20+
# * Configure and make a Python for each supported iOS/tvOS architecture and ABI
1921
# * Combine the outputs of the builds from the previous step into a single
2022
# XCframework, merging binaries into a "fat" binary if necessary
2123
# * Clone a copy of the testbed, configured to use the XCframework
@@ -76,6 +78,32 @@
7678
"x86_64-apple-ios-simulator": "x86_64-iphonesimulator",
7779
},
7880
},
81+
"tvOS": {
82+
"tvos-arm64": {
83+
"arm64-apple-tvos": "arm64-appletvos",
84+
},
85+
"tvos-arm64_x86_64-simulator": {
86+
"arm64-apple-tvos-simulator": "arm64-appletvsimulator",
87+
"x86_64-apple-tvos-simulator": "x86_64-appletvsimulator",
88+
},
89+
},
90+
"visionOS": {
91+
"xros-arm64": {
92+
"arm64-apple-xros": "arm64-xros",
93+
},
94+
"xros-arm64-simulator": {
95+
"arm64-apple-xros-simulator": "arm64-xrsimulator",
96+
},
97+
},
98+
"watchOS": {
99+
"watchos-arm64_32": {
100+
"arm64_32-apple-watchos": "arm64_32-watchos",
101+
},
102+
"watchos-arm64_x86_64-simulator": {
103+
"arm64-apple-watchos-simulator": "arm64-watchsimulator",
104+
"x86_64-apple-watchos-simulator": "x86_64-watchsimulator",
105+
},
106+
},
79107
}
80108

81109

@@ -137,12 +165,25 @@ def print_env(env: EnvironmentT) -> None:
137165
print(f"export {key}={shlex.quote(value)}")
138166

139167

168+
def platform_for_host(host):
169+
"""Determine the platform for a given host triple."""
170+
for plat, slices in HOSTS.items():
171+
for _, candidates in slices.items():
172+
for candidate in candidates:
173+
if candidate == host:
174+
return plat
175+
raise KeyError(host)
176+
177+
140178
def apple_env(host: str) -> EnvironmentT:
141179
"""Construct an Apple development environment for the given host."""
142180
env = {
143181
"PATH": ":".join(
144182
[
145-
str(PYTHON_DIR / "Apple/iOS/Resources/bin"),
183+
str(
184+
PYTHON_DIR
185+
/ f"Apple/{platform_for_host(host)}/Resources/bin"
186+
),
146187
str(subdir(host) / "prefix"),
147188
"/usr/bin",
148189
"/bin",
@@ -309,8 +350,8 @@ def unpack_deps(
309350
Downloads binaries if they aren't already present. Downloads will be stored
310351
in provided cache directory.
311352
312-
On iOS, as a safety mechanism, any dynamic libraries will be purged from
313-
the unpacked dependencies.
353+
On non-macOS platforms, as a safety mechanism, any dynamic libraries will be
354+
purged from the unpacked dependencies.
314355
"""
315356
deps_url = "https://github.com/beeware/cpython-apple-source-deps/releases/download"
316357
for name_ver in [
@@ -328,9 +369,9 @@ def unpack_deps(
328369
)
329370
shutil.unpack_archive(archive_path, prefix_dir)
330371

331-
# Dynamic libraries will be preferentially linked over static;
332-
# On iOS, ensure that no dylibs are available in the prefix folder.
333-
if platform == "iOS":
372+
# Dynamic libraries will be preferentially linked over static; On non-macOS
373+
# platforms, ensure that no dylibs are available in the prefix folder.
374+
if platform != "macOS":
334375
for dylib in prefix_dir.glob("**/*.dylib"):
335376
dylib.unlink()
336377

@@ -394,6 +435,7 @@ def configure_host_python(
394435
f"--build={sysconfig.get_config_var('BUILD_GNU_TYPE')}",
395436
f"--with-build-python={build_python_path()}",
396437
"--with-system-libmpdec",
438+
"--enable-ipv6",
397439
"--enable-framework",
398440
# Dependent libraries.
399441
f"--with-openssl={prefix_dir}",
@@ -434,7 +476,10 @@ def framework_path(host_triple: str, multiarch: str) -> Path:
434476
:param host_triple: The host triple (e.g., arm64-apple-ios-simulator)
435477
:param multiarch: The multiarch identifier (e.g., arm64-simulator)
436478
"""
437-
return CROSS_BUILD_DIR / f"{host_triple}/Apple/iOS/Frameworks/{multiarch}"
479+
return (
480+
CROSS_BUILD_DIR
481+
/ f"{host_triple}/Apple/{platform_for_host(host_triple)}/Frameworks/{multiarch}"
482+
)
438483

439484

440485
def package_version(prefix_path: Path) -> str:
@@ -461,8 +506,7 @@ def package_version(prefix_path: Path) -> str:
461506

462507

463508
def lib_platform_files(dirname, names):
464-
"""A file filter that ignores platform-specific files in the lib directory.
465-
"""
509+
"""A file filter that ignores platform-specific files in the lib directory."""
466510
path = Path(dirname)
467511
if (
468512
path.parts[-3] == "lib"
@@ -492,7 +536,9 @@ def lib_non_platform_files(dirname, names):
492536
"""
493537
path = Path(dirname)
494538
if path.parts[-2] == "lib" and path.parts[-1].startswith("python"):
495-
return set(names) - lib_platform_files(dirname, names) - {"lib-dynload"}
539+
return (
540+
set(names) - lib_platform_files(dirname, names) - {"lib-dynload"}
541+
)
496542
else:
497543
return set()
498544

@@ -639,7 +685,7 @@ def create_xcframework(platform: str) -> str:
639685
host_path = (
640686
CROSS_BUILD_DIR
641687
/ host_triple
642-
/ "Apple/iOS/Frameworks"
688+
/ f"Apple/{platform}/Frameworks"
643689
/ multiarch
644690
)
645691
host_framework = host_path / "Python.framework"
@@ -652,7 +698,8 @@ def create_xcframework(platform: str) -> str:
652698
# statically link those libraries into a Framework, you become
653699
# responsible for providing a privacy manifest for that framework.
654700
xcprivacy_file = {
655-
"OpenSSL": subdir(host_triple) / "prefix/share/OpenSSL.xcprivacy"
701+
"OpenSSL": subdir(host_triple)
702+
/ "prefix/share/OpenSSL.xcprivacy"
656703
}
657704
print(f" - {multiarch} xcprivacy files")
658705
for module, lib in [
@@ -683,20 +730,22 @@ def package(context: argparse.Namespace) -> None:
683730
# Create an XCframework
684731
version = create_xcframework(context.platform)
685732

686-
# Clone testbed
687-
print()
688-
run(
689-
[
690-
sys.executable,
691-
"Apple/testbed",
692-
"clone",
693-
"--platform",
694-
context.platform,
695-
"--framework",
696-
CROSS_BUILD_DIR / context.platform / "Python.xcframework",
697-
CROSS_BUILD_DIR / context.platform / "testbed",
698-
]
699-
)
733+
# watchOS doesn't have a testbed (yet!)
734+
if context.platform != "watchOS":
735+
# Clone testbed
736+
print()
737+
run(
738+
[
739+
sys.executable,
740+
"Apple/testbed",
741+
"clone",
742+
"--platform",
743+
context.platform,
744+
"--framework",
745+
CROSS_BUILD_DIR / context.platform / "Python.xcframework",
746+
CROSS_BUILD_DIR / context.platform / "testbed",
747+
]
748+
)
700749

701750
# Build the final archive
702751
archive_name = (

Apple/iOS/Resources/Info.plist.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
<key>CFBundlePackageType</key>
1818
<string>FMWK</string>
1919
<key>CFBundleShortVersionString</key>
20-
<string>@VERSION@</string>
20+
<string>%VERSION%</string>
2121
<key>CFBundleLongVersionString</key>
2222
<string>%VERSION%, (c) 2001-2024 Python Software Foundation.</string>
2323
<key>CFBundleSignature</key>
2424
<string>????</string>
2525
<key>CFBundleVersion</key>
26-
<string>1</string>
26+
<string>%VERSION%</string>
2727
<key>CFBundleSupportedPlatforms</key>
2828
<array>
2929
<string>iPhoneOS</string>

Apple/testbed/Python.xcframework/Info.plist

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,98 @@
3535
<key>SupportedPlatformVariant</key>
3636
<string>simulator</string>
3737
</dict>
38+
<dict>
39+
<key>BinaryPath</key>
40+
<string>Python.framework/Python</string>
41+
<key>LibraryIdentifier</key>
42+
<string>tvos-arm64</string>
43+
<key>LibraryPath</key>
44+
<string>Python.framework</string>
45+
<key>SupportedArchitectures</key>
46+
<array>
47+
<string>arm64</string>
48+
</array>
49+
<key>SupportedPlatform</key>
50+
<string>tvos</string>
51+
</dict>
52+
<dict>
53+
<key>BinaryPath</key>
54+
<string>Python.framework/Python</string>
55+
<key>LibraryIdentifier</key>
56+
<string>tvos-arm64_x86_64-simulator</string>
57+
<key>LibraryPath</key>
58+
<string>Python.framework</string>
59+
<key>SupportedArchitectures</key>
60+
<array>
61+
<string>arm64</string>
62+
<string>x86_64</string>
63+
</array>
64+
<key>SupportedPlatform</key>
65+
<string>tvos</string>
66+
<key>SupportedPlatformVariant</key>
67+
<string>simulator</string>
68+
</dict>
69+
<dict>
70+
<key>BinaryPath</key>
71+
<string>Python.framework/Python</string>
72+
<key>LibraryIdentifier</key>
73+
<string>xros-arm64-simulator</string>
74+
<key>LibraryPath</key>
75+
<string>Python.framework</string>
76+
<key>SupportedArchitectures</key>
77+
<array>
78+
<string>arm64</string>
79+
</array>
80+
<key>SupportedPlatform</key>
81+
<string>xros</string>
82+
<key>SupportedPlatformVariant</key>
83+
<string>simulator</string>
84+
</dict>
85+
<dict>
86+
<key>BinaryPath</key>
87+
<string>Python.framework/Python</string>
88+
<key>LibraryIdentifier</key>
89+
<string>xros-arm64</string>
90+
<key>LibraryPath</key>
91+
<string>Python.framework</string>
92+
<key>SupportedArchitectures</key>
93+
<array>
94+
<string>arm64</string>
95+
</array>
96+
<key>SupportedPlatform</key>
97+
<string>xros</string>
98+
</dict>
99+
<dict>
100+
<key>BinaryPath</key>
101+
<string>Python.framework/Python</string>
102+
<key>LibraryIdentifier</key>
103+
<string>watchos-arm64_x86_64-simulator</string>
104+
<key>LibraryPath</key>
105+
<string>Python.framework</string>
106+
<key>SupportedArchitectures</key>
107+
<array>
108+
<string>arm64</string>
109+
<string>x86_64</string>
110+
</array>
111+
<key>SupportedPlatform</key>
112+
<string>watchos</string>
113+
<key>SupportedPlatformVariant</key>
114+
<string>simulator</string>
115+
</dict>
116+
<dict>
117+
<key>BinaryPath</key>
118+
<string>Python.framework/Python</string>
119+
<key>LibraryIdentifier</key>
120+
<string>watchos-arm64_32</string>
121+
<key>LibraryPath</key>
122+
<string>Python.framework</string>
123+
<key>SupportedArchitectures</key>
124+
<array>
125+
<string>arm64_32</string>
126+
</array>
127+
<key>SupportedPlatform</key>
128+
<string>watchos</string>
129+
</dict>
38130
</array>
39131
<key>CFBundlePackageType</key>
40132
<string>XFWK</string>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>en</string>
7+
<key>CFBundleExecutable</key>
8+
<string></string>
9+
<key>CFBundleIdentifier</key>
10+
<string></string>
11+
<key>CFBundleInfoDictionaryVersion</key>
12+
<string>6.0</string>
13+
<key>CFBundlePackageType</key>
14+
<string>APPL</string>
15+
<key>CFBundleShortVersionString</key>
16+
<string>1.0</string>
17+
<key>CFBundleSupportedPlatforms</key>
18+
<array>
19+
<string>tvOS</string>
20+
</array>
21+
<key>MinimumOSVersion</key>
22+
<string>9.0</string>
23+
<key>CFBundleVersion</key>
24+
<string>1</string>
25+
</dict>
26+
</plist>

Apple/testbed/Python.xcframework/build/utils.sh

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,38 @@ install_stdlib() {
3434
else
3535
SLICE_FOLDER="ios-arm64_x86_64-simulator"
3636
fi
37-
else
37+
elif [ "$EFFECTIVE_PLATFORM_NAME" = "-iphoneos" ]; then
3838
echo "Installing Python modules for iOS Device"
3939
SLICE_FOLDER="ios-arm64"
40+
elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvsimulator" ]; then
41+
echo "Installing Python modules for tvOS Simulator"
42+
if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/tvos-arm64-simulator" ]; then
43+
SLICE_FOLDER="tvos-arm64-simulator"
44+
else
45+
SLICE_FOLDER="tvos-arm64_x86_64-simulator"
46+
fi
47+
elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvos" ]; then
48+
echo "Installing Python modules for tvOS Device"
49+
SLICE_FOLDER="tvos-arm64"
50+
elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchsimulator" ]; then
51+
echo "Installing Python modules for watchOS Simulator"
52+
if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/watchos-arm64-simulator" ]; then
53+
SLICE_FOLDER="watchos-arm64-simulator"
54+
else
55+
SLICE_FOLDER="watchos-arm64_x86_64-simulator"
56+
fi
57+
elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchos" ]; then
58+
echo "Installing Python modules for watchOS Device"
59+
SLICE_FOLDER="watchos-arm64"
60+
elif [ "$EFFECTIVE_PLATFORM_NAME" = "-xrsimulator" ]; then
61+
echo "Installing Python modules for visionOS Simulator"
62+
SLICE_FOLDER="xros-arm64-simulator"
63+
elif [ "$EFFECTIVE_PLATFORM_NAME" = "-xros" ]; then
64+
echo "Installing Python modules for visionOS Device"
65+
SLICE_FOLDER="xros-arm64"
66+
else
67+
echo "Unsupported platform name $EFFECTIVE_PLATFORM_NAME"
68+
exit 1
4069
fi
4170

4271
# If the XCframework has a shared lib folder, then it's a full framework.

0 commit comments

Comments
 (0)