これはなに?
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 -lv | grep -A5 LC_BUILD の場合:
$ otool -lv ./build/Sample.xcframework/ios-arm64/iOS.framework/iOS
./build/Sample.xcframework/ios-arm64/iOS.framework/iOS | grep -A5 LC_BUILD
otool -arch -l の場合:
$ otool -arch arm64 -l ./build/Sample.xcframework/ios-arm64/iOS.framework/iOS
./build/Sample.xcframework/ios-arm64/iOS.framework/iOS
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedfacf 16777228 0 0x00 6 16 928 0x00100085
Load command 0
cmd LC_SEGMENT_64
cmdsize 232
segname __TEXT
vmaddr 0x0000000000000000
vmsize 0x0000000000008000
fileoff 0
filesize 32768
maxprot 0x00000005
initprot 0x00000005
nsects 2
flags 0x0
Section
sectname __text
segname __TEXT
addr 0x0000000000007fd0
size 0x0000000000000000
offset 32720
align 2^0 (1)
reloff 0
nreloc 0
flags 0x80000400
reserved1 0
reserved2 0
Section
sectname __const
segname __TEXT
addr 0x0000000000007fd0
size 0x0000000000000030
offset 32720
align 2^3 (8)
reloff 0
nreloc 0
flags 0x00000000
reserved1 0
reserved2 0
Load command 1
cmd LC_SEGMENT_64
cmdsize 152
segname __LLVM
vmaddr 0x0000000000008000
vmsize 0x0000000000004000
fileoff 32768
filesize 16384
maxprot 0x00000003
initprot 0x00000003
nsects 1
flags 0x4
Section
sectname __bundle
segname __LLVM
addr 0x0000000000008000
size 0x00000000000009b3
offset 32768
align 2^0 (1)
reloff 0
nreloc 0
flags 0x00000000
reserved1 0
reserved2 0
Load command 2
cmd LC_SEGMENT_64
cmdsize 72
segname __LINKEDIT
vmaddr 0x000000000000c000
vmsize 0x00000000000000a8
fileoff 49152
filesize 168
maxprot 0x00000001
initprot 0x00000001
nsects 0
flags 0x0
Load command 3
cmd LC_ID_DYLIB
cmdsize 56
name @rpath/iOS.framework/iOS (offset 24)
time stamp 1 Thu Jan 1 09:00:01 1970
current version 1.0.0
compatibility version 1.0.0
Load command 4
cmd LC_DYLD_INFO_ONLY
cmdsize 48
rebase_off 0
rebase_size 0
bind_off 0
bind_size 0
weak_bind_off 0
weak_bind_size 0
lazy_bind_off 0
lazy_bind_size 0
export_off 49152
export_size 48
Load command 5
cmd LC_SYMTAB
cmdsize 24
symoff 49208
nsyms 3
stroff 49256
strsize 64
Load command 6
cmd LC_DYSYMTAB
cmdsize 80
ilocalsym 0
nlocalsym 0
iextdefsym 0
nextdefsym 2
iundefsym 2
nundefsym 1
tocoff 0
ntoc 0
modtaboff 0
nmodtab 0
extrefsymoff 0
nextrefsyms 0
indirectsymoff 0
nindirectsyms 0
extreloff 0
nextrel 0
locreloff 0
nlocrel 0
Load command 7
cmd LC_UUID
cmdsize 24
uuid FF4E63B0-7561-39BA-AABC-86C790D038FD
Load command 8
cmd LC_BUILD_VERSION
cmdsize 32
platform 2
sdk 13.2
minos 13.2
ntools 1
tool 3
version 520.0
Load command 9
cmd LC_SOURCE_VERSION
cmdsize 16
version 0.0
Load command 10
cmd LC_ENCRYPTION_INFO_64
cmdsize 24
cryptoff 16384
cryptsize 16384
cryptid 0
pad 0
Load command 11
cmd LC_LOAD_DYLIB
cmdsize 56
name /usr/lib/libSystem.B.dylib (offset 24)
time stamp 2 Thu Jan 1 09:00:02 1970
current version 1281.0.0
compatibility version 1.0.0
Load command 12
cmd LC_RPATH
cmdsize 40
path @executable_path/Frameworks (offset 12)
Load command 13
cmd LC_RPATH
cmdsize 40
path @loader_path/Frameworks (offset 12)
Load command 14
cmd LC_FUNCTION_STARTS
cmdsize 16
dataoff 49200
datasize 8
Load command 15
cmd LC_DATA_IN_CODE
cmdsize 16
dataoff 49208
datasize 0
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を生成するスクリプトになっています。よければそちらも参考にしてみて下さい!