これはなに?
2019年WWDCで発表された XCFramework に対応する際のポイント集です。
プロジェクト作成
【Tips】 新規プロジェクト作成から始める場合はEmpty
を選択する
Emptyを選択すると、余計なビルド設定がない状態でプロジェクト作成が出来るので可読性が良くなります。
① File > New > Project... > Cross-platform > Other > Empty と選び、新規プロジェクトを作成する。
②ターゲットが空のプロジェクトが作成されるので、サポートしたいプラットフォームの数だけターゲットを追加する。
File > New > Target... > [プラットフォーム] > Frameworks
iOS macOS tvOS watchOS を追加した場合:
③コマンドでプロジェクトの構成を確認するには
$ xcodebuild -list -json
{
"project" : {
"configurations" : [
"Debug",
"Release"
],
"name" : "Sample",
"schemes" : [
"iOS",
"macOS",
"tvOS",
"watchOS"
],
"targets" : [
"iOS",
"macOS",
"tvOS",
"watchOS"
]
}
}
ビルド設定
【必須】 Deployment Target を設定する
①フレームワークがサポートしたいOSバージョンの範囲の内、下限バージョンを指定します。
【オプション】 プロセッサアーキテクチャを指定する
※この章の内容は必要に応じて設定してください。
※Xcode 12以降のバージョンで新規プロジェクトを作成した場合は、comment-1 も参照してください。
フレームワークがサポートするプロセッサアーキテクチャを初期値から変更したい場合は、ARCHS
とVALID_ARCHS
にパラメータを指定します。
その2つの初期値はBase SDK
とDeployment Target
の値に合わせて変動する(i)ので、Deployment Target
を正しく設定してから行ってください。
i) xcodebuildがプラットフォームに合わせて、最低限必要なアーキテクチャを設定してくれる。
①コマンドで現在の値を確認するには
$ xcodebuild -showBuildSettings -project Sample.xcodeproj -scheme iOS archive | grep -e ARCHS
ARCHS = arm64
ARCHS_STANDARD = arm64
ARCHS_STANDARD_32_64_BIT = armv7 arm64
ARCHS_STANDARD_32_BIT = armv7
ARCHS_STANDARD_64_BIT = arm64
ARCHS_STANDARD_INCLUDING_64_BIT = arm64
ARCHS_UNIVERSAL_IPHONE_OS = armv7 arm64
VALID_ARCHS = arm64 arm64e armv7 armv7s
②サポートしたいアーキテクチャを、ARCHS
に指定します。(ii)
③VALID_ARCHS
にはARCHS
で指定したパラメータを含めるように指定します。
ii) ARCHSにarm64eを含める対応をするには
活用例:
- iOS 8.0以上がサポートするデバイスのアーキテクチャは arm64 armv7 armv7s の3つあるが、初期値ではARCHSにarmv7sが含まれていないので追加する。
# IPHONEOS_DEPLOYMENT_TARGET=8.0 に設定した場合のビルド設定を出力する
# ARCHS = armv7 arm64 になっている
$ xcodebuild -showBuildSettings -project Sample.xcodeproj -scheme iOS archive 'IPHONEOS_DEPLOYMENT_TARGET=8.0' | grep -e ARCHS
ARCHS = armv7 arm64
ARCHS_STANDARD = armv7 arm64
ARCHS_STANDARD_32_64_BIT = armv7 arm64
ARCHS_STANDARD_32_BIT = armv7
ARCHS_STANDARD_64_BIT = arm64
ARCHS_STANDARD_INCLUDING_64_BIT = armv7 arm64
ARCHS_UNIVERSAL_IPHONE_OS = armv7 arm64
VALID_ARCHS = arm64 arm64e armv7 armv7s
# ARCHS=arm64 armv7 armv7s を指定する
$ xcodebuild -showBuildSettings -project Sample.xcodeproj -scheme iOS archive 'IPHONEOS_DEPLOYMENT_TARGET=8.0' 'ARCHS=arm64 armv7 armv7s' | grep -e ARCHS
ARCHS = arm64 armv7 armv7s # Build settings from command line: の出力
ARCHS = arm64 armv7 armv7s # Build settings for action archive and target iOS: の出力
ARCHS_STANDARD = armv7 arm64
ARCHS_STANDARD_32_64_BIT = armv7 arm64
ARCHS_STANDARD_32_BIT = armv7
ARCHS_STANDARD_64_BIT = arm64
ARCHS_STANDARD_INCLUDING_64_BIT = armv7 arm64
ARCHS_UNIVERSAL_IPHONE_OS = armv7 arm64
VALID_ARCHS = arm64 arm64e armv7 armv7s
Architectures (ARCHS)
製品が構築されるアーキテクチャのリスト。 これは通常、プラットフォームによって提供される事前定義されたビルド設定に設定されます。 複数のアーキテクチャが指定されている場合、ユニバーサルバイナリが生成されます。
Valid Architectures (VALID_ARCHS)
ターゲットを実際に構築するアーキテクチャのスペースで区切られたリスト。 ターゲットごとに、これはアーキテクチャ(ARCHS)で指定されたリストと横断し、結果のセットが構築されます。 これにより、個々のターゲットが特定のアーキテクチャのビルドをオプトアウトできます。 結果のアーキテクチャのセットが空の場合、実行可能ファイルは生成されません。Base SDK (SDKROOT)
ビルド中に使用されているベースSDKの名前またはパス。 製品は、指定されたSDK内にあるヘッダーとライブラリに対して構築されます。 このパスはすべての検索パスの前に追加され、環境を介してコンパイラーとリンカーに渡されます。 追加のSDKは、追加のSDK(ADDITIONAL_SDKS)設定で指定できます。
デバイスのプロセッサアーキテクチャを調べるには:
- Apple-designed processors - Wikipedia
- iOS and iPadOS
- tvOS
- watchOS
Troubleshooting:
【必須】 Mach-O Type を設定する
①動的なXCFrameworkを生成したい場合は、Mach-O
にDynamic Library
を指定します。
静的なXCFrameworkを生成したい場合は、Mach-O
にStatic Library
を指定します。
【必須】 ビットコードに対応する
ビットコードを有効にするために、この3つのビルド設定を指定します。
ENABLE_BITCODE=YES
BITCODE_GENERATION_MODE=bitcode
OTHER_CFLAGS=-fembed-bitcode
Enable Bitcode (ENABLE_BITCODE)
この設定を有効にすると、ターゲットまたはプロジェクトは、それをサポートするプラットフォームおよびアーキテクチャのコンパイル中にビットコードを生成する必要があります。 アーカイブビルドの場合、App Storeに送信するために、リンクされたバイナリでビットコードが生成されます。 他のビルドの場合、コンパイラーとリンカーは、コードがビットコード生成の要件を満たしているかどうかを確認しますが、実際のビットコードは生成しません。
BITCODE_GENERATION_MODE
Xcodeの隠しコマンドなのでXcode Helpには記載がないが、clangのヘルプには記述が少しあります。
$ clang --help | grep bitcode
-fembed-bitcode-marker Embed placeholder LLVM IR data as a marker
-fembed-bitcode=<option>
Embed LLVM bitcode (option: off, all, bitcode, marker)
-fembed-bitcode Embed LLVM IR bitcode as data
Other C Flags (OTHER_CFLAGS)
CおよびObjective-Cファイルのコンパイラに渡す追加フラグのスペース区切りリスト。 スペースまたは特殊文字(スペースを含む可能性のあるパス名など)を含む引数は、必ずバックスラッシュでエスケープしてください。 Xcodeが特定のCまたはObjective-CコンパイラフラグのUIをまだ提供していない場合は、この設定を使用します。
【必須】 XCFrameworkに対応する
① XCFramework形式(.xcframework)で配布するために、この2つのビルド設定を指定します。
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
SKIP_INSTALL=NO
Build Libraries for Distribution (BUILD_LIBRARY_FOR_DISTRIBUTION)
ライブラリが配布用にビルドされていることを確認します。 Swiftの場合、これによりライブラリの進化とモジュールインターフェイスファイルの生成がサポートされます。
Skip Install (SKIP_INSTALL)
有効にした場合、展開場所がアクティブであってもビルドされた製品をインストールしません。
②destination
はこの様に指定します。
-destination 'generic/platform=iOS'
-destination 'generic/platform=iOS Simulator'
-destination 'generic/platform=macOS,variant=Mac Catalyst'
-destination 'generic/platform=iPadOS'
-destination 'generic/platform=iPadOS Simulator'
-destination 'generic/platform=macOS'
-destination 'generic/platform=tvOS'
-destination 'generic/platform=tvOS Simulator'
-destination 'generic/platform=watchOS'
-destination 'generic/platform=watchOS Simulator'
-destination 'generic/platform=carPlayOS'
-destination 'generic/platform=carPlayOS Simulator'
【ビルド設定のまとめ】
①まずxcodebuild archive
コマンドをサポートしたいフォーマット(-destination)の回数分実行します。そうすることで複数個の*.xcarchive
ファイルが生成されます。
②その後xcodebuild -create-xcframework
で、全ての*.xcarchive
ファイルを1つのパッケージにまとめます(*.xcframework にします)。
STEP 1/2: xcodebuild archive
プロセッサアーキテクチャを指定しない場合
$ xcodebuild \
'ENABLE_BITCODE=YES' \
'BITCODE_GENERATION_MODE=bitcode' \
'OTHER_CFLAGS=-fembed-bitcode' \
'BUILD_LIBRARY_FOR_DISTRIBUTION=YES' \
'SKIP_INSTALL=NO' \
archive \
-project 'Sample.xcodeproj' \
-scheme 'iOS' \
-destination 'generic/platform=iOS' \
-configuration 'Release' \
-archivePath 'build/Sample-iOS.xcarchive'
プロセッサアーキテクチャを指定する場合
$ xcodebuild \
'ARCHS=arm64 armv7 armv7s' \
'VALID_ARCHS = arm64 arm64e armv7 armv7s' \
'ENABLE_BITCODE=YES' \
'BITCODE_GENERATION_MODE=bitcode' \
'OTHER_CFLAGS=-fembed-bitcode' \
'BUILD_LIBRARY_FOR_DISTRIBUTION=YES' \
'SKIP_INSTALL=NO' \
archive \
-project 'Sample.xcodeproj' \
-scheme 'iOS' \
-destination 'generic/platform=iOS' \
-configuration 'Release' \
-archivePath 'build/Sample-iOS.xcarchive'
STEP 2/2: xcodebuild -create-xcframework
-framework
にはxcodebuild archive
で生成した全ての*.framework
のパスを記述します。
$ xcodebuild \
-create-xcframework \
-framework 'build/Sample-iOS.xcarchive/Products/Library/Frameworks/iOS.framework' \
-framework 'build/Sample-iOS-Simulator.xcarchive/Products/Library/Frameworks/iOS.framework' \
-framework 'build/Sample-macOS.xcarchive/Products/Library/Frameworks/macOS.framework' \
...
...
-output 'build/Sample.xcframework'
成果物の確認方法
.xcframeworkファイルが、ビルド設定で指定した通りに生成出来たかの確認方法
cat
コマンドでプロパティリストを出力するか、file
または lipo -info
を用いてバイナリ情報が対応しているアーキテクチャを出力出来ます。
cat の場合:
$ cat ./build/Sample.xcframework/Info.plist
<?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>AvailableLibraries</key>
<array>
<dict>
<key>LibraryIdentifier</key>
<string>watchos-armv7k_arm64_32</string>
<key>LibraryPath</key>
<string>watchOS.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>armv7k</string>
<string>arm64_32</string>
</array>
<key>SupportedPlatform</key>
<string>watchos</string>
</dict>
<dict>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>iOS.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
<dict>
<key>LibraryIdentifier</key>
<string>ios-x86_64-simulator</string>
<key>LibraryPath</key>
<string>iOS.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>LibraryIdentifier</key>
<string>macos-x86_64</string>
<key>LibraryPath</key>
<string>macOS.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>macos</string>
</dict>
<dict>
<key>LibraryIdentifier</key>
<string>watchos-i386-simulator</string>
<key>LibraryPath</key>
<string>watchOS.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>i386</string>
</array>
<key>SupportedPlatform</key>
<string>watchos</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>LibraryIdentifier</key>
<string>tvos-x86_64-simulator</string>
<key>LibraryPath</key>
<string>tvOS.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>tvos</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>LibraryIdentifier</key>
<string>tvos-arm64</string>
<key>LibraryPath</key>
<string>tvOS.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>tvos</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>
file の場合:
# 単一のアーキテクチャに対応しているバイナリの場合の出力結果
$ file ./build/Sample.xcframework/ios-arm64/iOS.framework/iOS
./build/Sample.xcframework/ios-arm64/iOS.framework/iOS: Mach-O 64-bit dynamically linked shared library arm64
# 複数のアーキテクチャに対応しているバイナリの場合の出力結果
$ file ./build/Sample.xcframework/watchos-armv7k_arm64_32/watchOS.framework/watchOS
./build/Sample.xcframework/watchos-armv7k_arm64_32/watchOS.framework/watchOS: Mach-O universal binary with 2 architectures: [arm_v7k:Mach-O dynamically linked shared library arm_v7k] [arm64_32_v8:Mach-O dynamically linked shared library arm64_32_v8]
./build/Sample.xcframework/watchos-armv7k_arm64_32/watchOS.framework/watchOS (for architecture armv7k): Mach-O dynamically linked shared library arm_v7k
./build/Sample.xcframework/watchos-armv7k_arm64_32/watchOS.framework/watchOS (for architecture arm64_32): Mach-O dynamically linked shared library arm64_32_v8
lipo -info の場合:
# 単一のアーキテクチャに対応しているバイナリの場合の出力結果
$ lipo -info ./build/Sample.xcframework/ios-arm64/iOS.framework/iOS
Non-fat file: ./build/Sample.xcframework/ios-arm64/iOS.framework/iOS is architecture: arm64
# 複数のアーキテクチャに対応しているバイナリの場合の出力結果
$ lipo -info ./build/Sample.xcframework/watchos-armv7k_arm64_32/watchOS.framework/watchOS
Architectures in the fat file: ./build/Sample.xcframework/watchos-armv7k_arm64_32/watchOS.framework/watchOS are: armv7k arm64_32
また otool
コマンドを用いると、バイナリ詳細情報が出力されます。
otoolコマンドでバイナリ詳細情報を出力し、LC_VERSION_MIN_IPHONEOS
が出力されればiOS実機向けにコンパイルされているかクラスが含まれている。
$ otool -arch arm64 -lv ./build/Sample.xcframework/ios-arm64/iOS.framework/iOS | grep -A 5 LC_VERSION_MIN_IPHONEOS
cmd LC_VERSION_MIN_IPHONEOS
cmdsize 16
version 11.0
sdk 18.2
Load command 2
cmd LC_LINKER_OPTIMIZATION_HINT
--
cmd LC_VERSION_MIN_IPHONEOS
cmdsize 16
version 11.0
sdk 18.2
Load command 2
cmd LC_LINKER_OPTIMIZATION_HINT
otoolコマンドでバイナリ詳細情報を出力し、LC_BUILD_VERSION
が出力されればiOS Simulator向けにコンパイルされているかクラスが含まれている。
$ otool -arch arm64 -lv ./build/Sample.xcframework/ios-arm64_x86_64-simulator/iOS.framework/iOS | grep -A 5 LC_BUILD_VERSION
cmd LC_BUILD_VERSION
cmdsize 32
platform IOSSIMULATOR
minos 13.0
sdk 17.5
ntools 1
--
cmd LC_BUILD_VERSION
cmdsize 32
platform IOSSIMULATOR
minos 14.0
sdk 17.5
ntools 1
otool -arch が対応しているフラグ一覧:
any little big ppc64 x86_64 x86_64h arm64 ppc970-64 arm64_32 arm64e ppc i386 m68k hppa sparc m88k i860 veo arm ppc601 ppc603 ppc603e ppc603ev ppc604 ppc604e ppc750 ppc7400 ppc7450 ppc970 i486 i486SX pentium i586 pentpro i686 pentIIm3 pentIIm5 pentium4 m68030 m68040 hppa7100LC veo1 veo2 veo3 veo4 armv4t armv5 xscale armv6 armv6m armv7 armv7f armv7s armv7k armv7m armv7em arm64v8
Debug symbol (dSYM)
dSYMファイルとは:
アプリクラッシュ時に出力されるスタックトレースを復元する場合に必要な、シンボルファイルです。
復元される主な情報は、作成したframeworkのクラス名・メソッド名です。
必要に応じて、XCFrameworkと同様に配布するのがいいかと思います。
frameworkのdSYMファイルはどこに生成されるのか?
dSYMファイルは xcodebuild archive で生成した *.xcarchive
ファイルの配下 (*.xcarchive/dSYMs/*.framework.dSYM) に保存されます。
Bitcode Symbol Maps (BCSymbolMaps)
BCSymbolMapsファイルとは:
ビットコードを有効にしたときに生成される、ビルド時の設定値が記述されたテキストファイルです。
frameworkのBCSymbolMapsファイルはどこに生成されるのか?
BCSymbolMapsファイルは xcodebuild archive で生成した *.xcarchive
ファイルの配下 (*.xcarchive/BCSymbolMaps/*.bcsymbolmap) に保存されます。
XCFrameworkを配布する
CarthageやSwift PMなどで配布したい場合は、関連するソフトウェアの対応状況の各種リンクを参照してください。
CocoaPodsで配布する
XCFrameworkをCocoaPodsで配布するための手順と、追加で指定するパラメータについて。
① 生成した*.xcframework
ファイルをzipなどにアーカイブする。
② HTTP(S)で、zipファイルを配布します。
AWS S3や自社HTTPサーバー、GitHub Releasesなどでzipを配布するのが一般的だと思います。
③ source に http
を選択し、zipファイルのURLを指定する。
Using HTTP to download a compressed file of the code. It supports zip, tgz, bz2, txz and tar.
spec.source = { :http => 'http://dev.wechatapp.com/download/sdk/WeChat_SDK_iOS_en.zip'
④ vendored_frameworks や ios.vendored_frameworks などを指定する
⑤ platform か deployment_target を指定する
⑥ cocoapods_version に >= 1.9.0
を指定する
⑦ swift_versions と swift_version を指定する
その他
参考にした資料
- XCFramework とは?
- XCFramework を作成する
- XCFramework のサンプルコード
- いろいろ
お手軽にXCFramework対応したい場合に良さそうなソフトウェア
- segment-integrations/swift-create-xcframework: xcodebuild をラップして XCFrameworks を作成するためのシンプルなコマンドライン ツール。
- josercc/XCFrameworkBuild: A script program that helps you build XCFramework quickly, and supports conversion of .framework and .a before .xcframework.
関連するソフトウェアの対応状況
- CocoaPods: 1.9.0で対応した
- Carthage: 0.37.0でソースコードからのXCFrameworks生成に対応した。0.38.0でバイナリをダウンロードしてのXCFrameworksに対応した
-
Swift PM: Xcode 12.0 Beta/Swift 5.3で対応した
- [PITCH] Support for binary dependencies - Development / Package Manager - Swift Forums
- swift-evolution/0271-package-manager-resources.md at master · apple/swift-evolution
- swift-evolution/0272-swiftpm-binary-dependencies.md at master · apple/swift-evolution
-
Xcode 12 Beta Release Notes | Apple Developer Documentation
- Swift Packages
- fastlane: スクリプトを自前で実装するか、CocoaPods(1.9.0)と組み合わせることで実装が出来る
- Kotlin Multiplatform: JetBrains提供のフレームワークを用いて、XCFrameworksを生成出来る
執筆時の環境
- macOS: 10.14.5
- Xcode: 11.3.1
今回作成したプロジェクトとスクリプト
-
Arime9/XCFramework-Sample: This is sample code for creating an XCFramework.
- build-scripts/create.sh と archive.json は、XCFrameworkを生成するスクリプトになっています。よければそちらも参考にしてみて下さい!