In this dependency graph:
MyBinaryFramework -> CMyBinaryFramework -> MyLibrary
You can safely remove the CMyBinaryFramework dependency as it is unnecessary, Swift Package Manager supports binary target(xcframeworks). Your MyLibrary can use c/c++ public symbols from MyBinaryFramework directly if you provide module.modulemap in the framework.
Your MyBinaryFramework.xcframework's folder structure should look like this:
.
├── Info.plist
├── ios-arm64
│ └── libavformat.framework
│ ├── Headers
│ │ ├── avformat.h
│ │ ├── avio.h
│ │ ├── config.h
│ │ ├── os_support.h
│ │ ├── version_major.h
│ │ └── version.h
│ ├── Info.plist
│ ├── libavformat
│ └── Modules
│ └── module.modulemap
├── ios-arm64_x86_64-simulator
│ └── libavformat.framework
│ ├── Headers
│ │ ├── avformat.h
│ │ ├── avio.h
│ │ ├── config.h
│ │ ├── os_support.h
│ │ ├── version_major.h
│ │ └── version.h
│ ├── Info.plist
│ ├── libavformat
│ └── Modules
│ └── module.modulemap
├── macos-arm64_x86_64
│ └── libavformat.framework
│ ├── Headers -> Versions/Current/Headers
│ ├── libavformat -> Versions/Current/libavformat
│ ├── Modules
│ │ └── module.modulemap
│ ├── Resources -> Versions/Current/Resources
│ └── Versions
│ ├── A
│ │ ├── Headers
│ │ │ ├── avformat.h
│ │ │ ├── avio.h
│ │ │ ├── config.h
│ │ │ ├── os_support.h
│ │ │ ├── version_major.h
│ │ │ └── version.h
│ │ ├── libavformat
│ │ └── Resources
│ │ └── Info.plist
│ └── Current -> A
├── tvos-arm64
│ └── libavformat.framework
│ ├── Headers
│ │ ├── avformat.h
│ │ ├── avio.h
│ │ ├── config.h
│ │ ├── os_support.h
│ │ ├── version_major.h
│ │ └── version.h
│ ├── Info.plist
│ ├── libavformat
│ └── Modules
│ └── module.modulemap
└── tvos-arm64_x86_64-simulator
└── libavformat.framework
├── Headers
│ ├── avformat.h
│ ├── avio.h
│ ├── config.h
│ ├── os_support.h
│ ├── version_major.h
│ └── version.h
├── Info.plist
├── libavformat
└── Modules
└── module.modulemap
27 directories, 47 files
This legal bundle structure is addressed in Placing content in a bundle, heads up:
MacOS uses a structure called versioned bundle to support multiple library versions. Using iOS's bundle structure on a macOS target allows you to run your app, but you can't finally archive it. It will be mentioned in below.
If your xcframework is correctly made, you don't need to wrap it with SPM. Just drag it into Xcode, and your swift code can use that framework.
Below are the steps I used to create a multi-platform xcframework from command line, I strongly recommend you to follow the steps:
- Build for Each Platform/Architecture
First, build your library for each platform and architecture combination, the library can be static(.a) or dynamic(.dylib):
# Build for each platform (iOS, macOS, tvOS, etc.) and architecture (arm64, x86_64)
- Create fat/universal libraries using
lipo for each platform, including iOS, iOS Simulator, macOS, tvOS, and tvOS Simulator, among others.
Combine architectures for each platform:
# Static library
lipo -create \
./build/ios-simulator/thin/arm64/lib/libmylib.a \
./build/ios-simulator/thin/x86_64/lib/libmylib.a \
-output ./mylib/ios-simulator/libmylib.framework/libmylib
# Or Dynamic library
lipo -create \
./build/ios-simulator/thin/arm64/lib/libmylib.dylib \
./build/ios-simulator/thin/x86_64/lib/libmylib.dylib \
-output ./mylib/ios-simulator/libmylib.framework/libmylib
- Now we have a fat
libmylib, we then need to Create Framework Bundle Structure
For iOS/tvOS (flat structure), they are just folders in this structure:
libmylib.framework/
├── libmylib # The binary
├── Headers/ # Public headers
│ └── *.h
├── Modules/
│ └── module.modulemap
└── Info.plist
For macOS (versioned bundle structure, -> means symbolic link, create them with ln, e.g. ln -s Versions/Current/libmylib libmylib):
libmylib.framework/
├── libmylib -> Versions/Current/libmylib
├── Headers -> Versions/Current/Headers
├── Resources -> Versions/Current/Resources
├── Modules/
│ └── module.modulemap
└── Versions/
├── A/
│ ├── libmylib # The actual binary
│ ├── Headers/
│ │ └── *.h
│ └── Resources/
│ └── Info.plist
└── Current -> A
You can omit the mac platform if you don't need it.
- Create
module.modulemap(required if you need to export public symbols to swift)
create that file with this content:
framework module libmylib [system] {
umbrella "."
exclude header "internal.h" # Optional: exclude private headers
export *
}
- Create
Info.plist
You may need to modify the CFBundleSupportedPlatforms in each plist, they may be one of these values:
- iPhoneOS: For applications or frameworks intended for iOS devices (actual iPhones, iPads).
- iPhoneSimulator: For applications or frameworks intended for the iOS Simulator.
- MacOSX: For applications or frameworks intended for macOS.
- AppleTVOS: For applications or frameworks intended for tvOS devices.
- AppleTVSimulator: For applications or frameworks intended for the tvOS Simulator.
- WatchOS: For applications or frameworks intended for watchOS devices.
- WatchSimulator: For applications or frameworks intended for the watchOS
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>libmylib</string>
<key>CFBundleIdentifier</key>
<string>com.example.libmylib</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>libmylib</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
</dict>
</plist>
- Create XCFramework with
xcodebuild
With the libmylib.framework folder structure ready for each platform, time to create a XCFramework with xcodebuild
xcodebuild -create-xcframework \
-framework ./mylib/ios/libmylib.framework \
-framework ./mylib/iossimulator/libmylib.framework \
-framework ./mylib/macos/libmylib.framework \
-framework ./mylib/tvos/libmylib.framework \
-framework ./mylib/tvossimulator/libmylib.framework \
-output ./Sources/libmylib.xcframework
- Integrating with Swift Package Manager
Finally you can integrate with SPM, just put the xcframework in Sources folder
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MyLibrary",
platforms: [
.iOS(.v13),
.macOS(.v10_15),
.tvOS(.v13)
],
products: [
.library(name: "MyLibrary", targets: ["MyLibrary"])
],
targets: [
// Binary target for XCFramework
.binaryTarget(
name: "libmylib",
path: "Sources/libmylib.xcframework"
),
// Swift wrapper target (optional)
.target(
name: "MyLibrary",
dependencies: ["libmylib"],
linkerSettings: [
.linkedFramework("VideoToolbox"),
.linkedFramework("CoreMedia"),
.linkedLibrary("z"),
.linkedLibrary("bz2")
]
)
]
)
Comment below if you need some help!