-
-
Notifications
You must be signed in to change notification settings - Fork 3
Build System
This document explains how ThorVG Swift builds and packages the ThorVG library for distribution via Swift Package Manager.
ThorVGSwift uses a pre-built binary approach rather than compiling C++ source directly through Swift Package Manager. The build process:
- Compiles ThorVG as a static library (
.afiles) using its native Meson build system - Builds for multiple platforms: macOS (arm64 + x86_64), iOS (arm64), iOS Simulator (arm64)
- Packages everything into a single XCFramework that Swift Package Manager can use
This approach provides several benefits:
- ✅ Simplicity: Users don't need Meson or complex build tools
- ✅ Speed: No compilation time for end users
- ✅ Maintenance: ThorVG's build system handles platform-specific complexity
- ✅ Reliability: Consistent builds across all developer environments
The entire build process is automated by scripts/build_frameworks.sh, which:
# Automatically detects Xcode installation
XCODE_DEVELOPER_DIR=$(xcode-select -p)
# Finds SDK paths dynamically
IPHONEOS_SDK=$(xcrun --sdk iphoneos --show-sdk-path)
IPHONESIMULATOR_SDK=$(xcrun --sdk iphonesimulator --show-sdk-path)This ensures the build works regardless of whether you have Xcode.app, Xcode-26.app, or any other Xcode installation.
The script builds ThorVG separately for each target platform:
# Build for arm64
meson setup build/macosx-arm64 --buildtype=release ...
ninja -C build/macosx-arm64
# Build for x86_64
meson setup build/macosx-x86_64 --buildtype=release ...
ninja -C build/macosx-x86_64
# Combine into universal binary
lipo -create \
build/macosx-arm64/src/libthorvg.a \
build/macosx-x86_64/src/libthorvg.a \
-output libthorvg-macos.ameson setup build/iphoneos-arm64 \
--cross-file cross-iphoneos-arm64.txt \
--buildtype=release ...
ninja -C build/iphoneos-arm64meson setup build/iphonesimulator-arm64 \
--cross-file cross-iphonesimulator-arm64.txt \
--buildtype=release ...
ninja -C build/iphonesimulator-arm64The script creates an XCFramework with the correct structure:
ThorVG.xcframework/
├── Info.plist # Framework metadata
├── macos-arm64_x86_64/ # macOS universal binary
│ ├── libthorvg.a
│ └── Headers/
│ ├── thorvg_capi.h
│ └── module.modulemap
├── ios-arm64/ # iOS device
│ ├── libthorvg.a
│ └── Headers/
│ ├── thorvg_capi.h
│ └── module.modulemap
└── ios-arm64_x86_64-simulator/ # iOS Simulator
├── libthorvg.a
└── Headers/
├── thorvg_capi.h
└── module.modulemap
Each platform slice includes:
-
Static library (
.afile) with the compiled ThorVG code -
Header file (
thorvg_capi.h) with the C API declarations -
Module map (
module.modulemap) to expose the library to Swift
Cross-compilation is the process of building code on one platform (macOS) for execution on another (iOS device/simulator). The build script creates temporary cross-compilation files that tell Meson how to build for each target.
The critical distinction between iOS device and simulator builds is the target triple:
# iOS Device
arm64-apple-ios13.0 # Platform ID: 2 (iOS)
# iOS Simulator
arm64-apple-ios13.0-simulator # Platform ID: 7 (iOS Simulator)The -simulator suffix ensures Xcode correctly identifies simulator binaries. Without this, you'll get linker errors like:
Building for 'iOS-simulator', but linking in object file built for 'iOS'
Each cross-file specifies:
-
Compiler:
clang++with appropriate-targetflag - SDK path: Points to platform-specific SDK (iPhoneOS.sdk vs iPhoneSimulator.sdk)
- Minimum version: iOS 13.0 deployment target
- Host machine: CPU architecture and platform information
Example for iOS Simulator:
[binaries]
cpp = ['clang++', '-target', 'arm64-apple-ios13.0-simulator', '-isysroot', '<SDK_PATH>']
[host_machine]
system = 'darwin'
subsystem = 'ios'
cpu_family = 'aarch64'
cpu = 'aarch64'The build script uses carefully tuned Meson options for each platform:
-Ddefault_library=static # Build static library, not dynamic
-Dloaders=svg,tvg,lottie,ttf # Enable specific file format loaders
-Dsavers= # Disable savers (not needed for rendering)
-Dengines=sw # Use software rendering engine
-Dbindings=capi # Enable C API bindings (for Swift interop)
-Dexamples=false # Don't build examples
-Dtests=false # Don't build tests
-Dtools= # Don't build command-line tools
-Dlog=false # Disable logging
-Dextra=lottie_expressions # Enable Lottie expression support
-Dbuildtype=release # Optimize for release
-Dstrip=true # Strip debug symbols (smaller binary)-Dthreads=true # Enable pthread support (available on macOS)
-Dsimd=true # Enable SIMD optimizations (AVX on x86_64)Why threading works on macOS: pthread is a standard UNIX library that's always available on macOS.
Why SIMD works on macOS: Desktop CPUs support advanced SIMD instructions like AVX for faster rendering.
-Dthreads=false # Disable pthread (integrated into iOS system libs)
-Dsimd=false # Disable SIMD (avoid ARM-specific flags)Why threading is disabled on iOS: iOS integrates pthread directly into the system libraries. Trying to link against a separate pthread library causes build failures. Threading still works; we just don't explicitly link the library.
Why SIMD is disabled on iOS: Meson's SIMD detection tries to add -mfpu=neon which is invalid for ARM64 (that flag is for ARM32). Modern ARM64 CPUs have NEON built-in by default, so we don't need to explicitly enable it.
| Platform | Threads | SIMD | Performance Notes |
|---|---|---|---|
| macOS arm64 | ✅ | ✅ | Full multi-core + vector optimizations |
| macOS x86_64 | ✅ | ✅ | Full multi-core + AVX optimizations |
| iOS device | Single-threaded, but NEON is implicit | ||
| iOS simulator | Single-threaded, but NEON is implicit |
Note: iOS performance is still excellent for typical use cases. Most Lottie animations render in milliseconds even without explicit threading.
After running scripts/build_frameworks.sh, you'll have:
ThorVG.xcframework/ # ← Commit this to Git
├── Info.plist
├── ios-arm64/
├── ios-arm64_x86_64-simulator/
└── macos-arm64_x86_64/
build/ # ← Don't commit (in .gitignore)
├── cross-iphoneos-arm64.txt
├── cross-iphonesimulator-arm64.txt
├── iphoneos-arm64/
├── iphonesimulator-arm64/
├── macosx-arm64/
└── macosx-x86_64/
lib/ # ← Don't commit (in .gitignore)
└── libthorvg.a # Standalone macOS library (for local dev)
Only commit ThorVG.xcframework/ to the repository. This pre-built binary is essential for Swift Package Manager users who depend on your package. They download the XCFramework directly without needing to build ThorVG themselves.
Don't commit build/ or lib/ - these are temporary build artifacts added to .gitignore.
When a user adds ThorVGSwift to their Package.swift:
dependencies: [
.package(url: "https://github.com/thorvg/thorvg.swift", from: "0.1.0")
]SPM will:
- Clone the repository (including the committed
ThorVG.xcframework) - Read
Package.swiftand find the binary target - Select the appropriate platform slice:
- macOS build → uses
macos-arm64_x86_64/libthorvg.a - iOS device → uses
ios-arm64/libthorvg.a - iOS simulator → uses
ios-arm64_x86_64-simulator/libthorvg.a
- macOS build → uses
- Link the static library directly to the user's app
- Compile only the Swift wrapper code in
swift/
No C++ compilation happens for end users - they get instant builds!
To update to a newer version of ThorVG:
# 1. Update the submodule
cd thorvg
git fetch --tags
git checkout v0.15.16 # or whatever version you want
cd ..
# 2. Rebuild the XCFramework
rm -rf ThorVG.xcframework build lib
./scripts/build_frameworks.sh
# 3. Test
swift build
swift test # macOS tests
# Also test in Xcode with iOS Simulator
# 4. Commit the new XCFramework
git add thorvg ThorVG.xcframework
git commit -m "Update ThorVG to v0.15.16"brew install mesonThe script uses xcrun to find SDKs dynamically. Ensure Xcode is installed:
xcode-select --install
xcode-select -p # Should show your Xcode pathThis means the simulator binary wasn't properly tagged with the -simulator target triple. Rebuild:
rm -rf ThorVG.xcframework build
./scripts/build_frameworks.shThe XCFramework contains optimized static libraries for three platform variants. Users only download what they need. Typical sizes:
- macOS library: ~2-3 MB (universal binary)
- iOS library: ~1-2 MB (arm64 only)
- iOS Simulator library: ~1-2 MB (arm64 only)
Total in repository: ~5-8 MB (compressed by Git)
| Platform | Architectures | Min Version | Threading | SIMD |
|---|---|---|---|---|
| macOS | arm64, x86_64 | 10.15+ | ✅ | ✅ |
| iOS | arm64 | 13.0+ | System | Built-in |
| iOS Simulator | arm64 | 13.0+ | System | Built-in |
Note: Intel Mac users can run the arm64 simulator build via Rosetta 2. If you need native x86_64 simulator support, modify scripts/build_frameworks.sh to add an x86_64 simulator build and create a fat binary with lipo.