Site-Packages & Frameworks on iOS¶
This page explains how ksproject handles Python packages and xcframeworks during iOS/macOS builds — specifically how the .frameworks/ directory in site-packages is discovered, collected, and integrated into the Xcode project.
How Site-Packages Are Installed¶
When you build for an Apple platform, ksproject installs your project's Python dependencies cross-compiled for the target platform using uv pip install with platform-specific flags (--python-platform arm64-apple-ios, aarch64-apple-darwin, ...).
Each platform slice gets its own directory:
project_dist/xcode/site_packages/
├── iphoneos/ # Physical device (arm64)
│ ├── kivy/
│ ├── myapp/
│ └── ...
├── iphonesimulator/ # Simulator (arm64 or x86_64, matching your Mac)
│ └── ...
├── macos-arm64/ # macOS on Apple Silicon
│ └── ...
└── macos-x86_64/ # macOS on Intel
└── ...
During the Xcode build, an rsync build phase copies the matching slice into the app bundle (<App>.app/python/site_packages), where the Python runtime loads it at startup. On release builds, the synced packages are byte-compiled with a uv-managed interpreter matching the bundled CPython.
The .frameworks/ Convention¶
Some Python packages ship with xcframeworks — pre-compiled iOS/macOS dynamic libraries in Apple's universal framework format. These are distributed inside pip wheels under a .frameworks/ directory.
How It Works¶
- Package authors include
.frameworks/*.xcframeworkin their wheel (via custom build backends) - pip installs the wheel into site-packages, placing
.frameworks/alongside the Python code - ksproject detects
.frameworks/in site-packages after installation - Frameworks are moved from site-packages to
project_dist/xcode/Frameworks/ - The Xcode project is re-synced — if the set of xcframeworks changed,
project.ymlis rewritten and XcodeGen re-runs automatically before xcodebuild
Example Flow¶
Kivy's iOS wheels ship their SDL2 dependencies this way:
kivy-2.3.1-cp313-cp313-ios_15_6_arm64_iphoneos.whl
└── .frameworks/
├── SDL2.xcframework/
├── SDL2_image.xcframework/
├── SDL2_ttf.xcframework/
└── SDL2_mixer.xcframework/
After uv pip install:
site_packages/iphoneos/
├── .frameworks/
│ ├── SDL2.xcframework/
│ ├── SDL2_image.xcframework/
│ └── ...
└── kivy/
└── ...
ksproject then moves them:
project_dist/xcode/Frameworks/
├── Python.xcframework/ # From BeeWare
├── SDL2.xcframework/ # From the Kivy wheel
├── SDL2_image.xcframework/ # From the Kivy wheel
├── SDL2_ttf.xcframework/ # From the Kivy wheel
└── SDL2_mixer.xcframework/ # From the Kivy wheel
Why .frameworks/ Are Moved Out¶
xcframeworks cannot stay inside site-packages because:
- Xcode needs them at link time — They must be in a known framework search path
- Code signing — Frameworks must be in the app bundle's
Frameworks/directory - Size — Keeping them in assets would bloat the app with duplicate binaries
After moving, the .frameworks/ directory is removed from site-packages to prevent it from being bundled into the app's Python assets.
Framework Merging Across Platforms¶
When building for both iOS device and simulator, ksproject collects frameworks from all platform slices and merges them:
site_packages/iphoneos/.frameworks/
├── MyLib.xcframework/
│ └── ios-arm64/
│ └── MyLib.framework/
site_packages/iphonesimulator/.frameworks/
├── MyLib.xcframework/
│ └── ios-arm64_x86_64-simulator/
│ └── MyLib.framework/
Both get copied into Frameworks/ — xcframeworks already handle multi-platform via their internal slice structure.
How Frameworks Are Referenced in Xcode¶
The generated project.yml (XcodeGen spec) lists every .xcframework found in Frameworks/ as an embedded dependency of the app target, sets the framework search paths, and adds a copy frameworks build phase that embeds them into the app bundle at <App>.app/Frameworks/.
Because the spec is regenerated whenever the xcframework set changes (pip install a new framework-carrying package → next build picks it up), you never edit the Xcode project by hand for this. For manual additions, use the xcode.yaml overlay.
Pre-installed Frameworks¶
ksproject automatically provides one framework that doesn't come from pip:
| Framework | Source | Cached At |
|---|---|---|
| Python.xcframework | BeeWare Python-Apple-support | ~/.kivyschool/apple_support/ |
The Python xcframework contains both iOS and macOS slices, follows your .python-version pin, and is shared across all projects.
Creating Packages with Frameworks¶
If you're building a pip package that provides iOS frameworks, structure your wheel like this:
my_native_lib-1.0.0-cp313-cp313-ios_arm64.whl
├── my_native_lib/
│ ├── __init__.py
│ └── wrapper.py
└── .frameworks/
└── MyNativeLib.xcframework/
├── ios-arm64/
│ └── MyNativeLib.framework/
│ ├── MyNativeLib # Binary
│ ├── Headers/
│ └── Info.plist
└── ios-arm64_x86_64-simulator/
└── MyNativeLib.framework/
└── ...
When users install your package in a ksproject app, the framework will be automatically:
- Installed into site-packages with
.frameworks/ - Detected and moved to the Xcode project's
Frameworks/directory - Linked and embedded in the final
.appbundle
Comparison: Android vs iOS Native Assets¶
| Aspect | Android | iOS |
|---|---|---|
| Native library format | .so (shared objects) |
.xcframework (universal bundles) |
| Location in wheel | .libs/<abi>/*.so |
.frameworks/*.xcframework/ |
| Destination in project | jniLibs/<abi>/ |
Frameworks/ → <App>.app/Frameworks/ |
| Discovery mechanism | Scan site-packages after install | Scan site-packages after install |
| Multi-arch handling | Separate dirs per ABI | xcframework contains all slices |
Summary¶
The .frameworks/ convention enables pip packages to deliver native iOS/macOS code alongside Python packages. ksproject's build system:
- Installs packages with
uv pip install(cross-compiled for target platform) - Scans for
.frameworks/in the installed site-packages - Moves xcframeworks to the Xcode project's
Frameworks/directory - Re-syncs the XcodeGen spec so search paths and embedding stay correct
- Removes
.frameworks/from site-packages to avoid duplication in assets
This creates a seamless experience where pip install some-ios-library automatically makes native frameworks available in your mobile app — no manual Xcode configuration required.