現(xiàn)今 Swift 包中的二進(jìn)制目標(biāo)
文章目錄
- 理解二進(jìn)制在 Swift 中的演變。
- 命令行工具相關(guān)。
- 結(jié)論。
在 iOS 和 macOS 開發(fā)中, Swift 包現(xiàn)在變得越來越重要。Apple 已經(jīng)努力推動(dòng)橋接那些縫隙,并且修復(fù)那些阻礙開發(fā)者的問題,例如阻礙開發(fā)者將他們的庫和依賴由其他諸如 Carthage[1] 或 CocoaPods[2] 依賴管理工具遷移到 Swift 包依賴管理工具的問題,例如沒有能力添加構(gòu)建步驟的問題。這對任何依賴一些代碼生成的庫來說都是破壞者,比如,協(xié)議和 Swift 生成。
理解二進(jìn)制在 Swift 中的演變
為了充分理解 Apple 的 Swift 團(tuán)隊(duì)在二進(jìn)制目標(biāo)和他們引入的一些新 API 方面采取的一些步驟,我們需要理解它們從何而來。在后續(xù)的部分中,我們將調(diào)研 Apple 架構(gòu)的演變,以及為什么二進(jìn)制目標(biāo)的 API 在過去幾年中逐漸形成的,特別是自 Apple 發(fā)布了自己的硅芯片之后。
胖二進(jìn)制和 Frameworks 框架
如果你曾必須處理二進(jìn)制依賴,或者你曾創(chuàng)建一個(gè)屬于你自己的可執(zhí)行文件,你將會(huì)對 胖二進(jìn)制 這個(gè)術(shù)語感到熟悉。這些被擴(kuò)展(或增大)的可執(zhí)行文件,是包含了為多個(gè)不同架構(gòu)原生構(gòu)建的切片。這允許庫的所有者分發(fā)一個(gè)運(yùn)行在所有預(yù)期的目標(biāo)架構(gòu)上的單獨(dú)的二進(jìn)制。
當(dāng)源碼不能被暴露或當(dāng)處理非常龐大的代碼倉庫時(shí),預(yù)編譯庫成為可執(zhí)行文件非常有意義,因?yàn)轭A(yù)編譯源碼以及以二進(jìn)制文件分發(fā)他們,將節(jié)省構(gòu)建程序在他們的應(yīng)用上的構(gòu)建時(shí)間。
Pods[3] 是一個(gè)非常好的例子,當(dāng)開發(fā)者發(fā)現(xiàn)他們自己沒必要構(gòu)建那些非常少改動(dòng)的依賴。這是一個(gè)很共通的問題,它激發(fā)了諸如 cocoapods-binary[4] 之類的項(xiàng)目,該項(xiàng)目預(yù)編譯了 pod 依賴項(xiàng)以減少客戶端的構(gòu)建時(shí)間。
Frameworks 框架
嵌入靜態(tài)二進(jìn)制文件可能對應(yīng)用程序來說已經(jīng)足夠了,但如果需要某些資源(如 assets 或頭文件),則需要將這些資源與包含所有切片的 胖二進(jìn)制文件 捆綁在一起,形成所謂的 frameworks 文件。
這就是諸如 Google Cast[5] 之類的預(yù)編譯庫在過渡到使用 xcframework 進(jìn)行分發(fā)之前所做的事情 —— 下一節(jié)將詳細(xì)介紹這種過渡的原因。
到目前為止,一切都很好。如果我們要為分發(fā)預(yù)編譯一個(gè)庫,那么胖二進(jìn)制文件聽起來很理想,對吧?并且,如果我們需要捆綁一些其他資源,我們可以只使用一個(gè) frameworks。一個(gè)二進(jìn)制來統(tǒng)治他們所有!
XCFrameworks 框架
好吧,不完全是。胖二進(jìn)制文件有一個(gè)大問題,那就是你不能有兩個(gè)架構(gòu)相同但命令/指令不同的切片。這曾經(jīng)很好,因?yàn)樵O(shè)備和模擬器的架構(gòu)總是不同的,但是隨著 Apple Silicon 計(jì)算機(jī) (M1) 的推出,模擬器和設(shè)備共享相同的架構(gòu) (arm64),但具有不同的加載器命令。這與面向未來的二進(jìn)制目標(biāo)相結(jié)合,正是 Apple 引入 XCFrameworks[6] 的原因。
你可以在 Bogo Giertler 撰寫的這篇精彩文章 中詳細(xì)了解為 iOS 設(shè)備構(gòu)建的 arm64 切片和為 M1 mac 的 iOS 模擬器構(gòu)建的 arm64 切片之間的區(qū)別。
?XCFrameworks[7] 現(xiàn)在允許將多個(gè)二進(jìn)制文件捆綁在一起,解決了 M1 Mac 引入的設(shè)備和模擬器沖突架構(gòu)問題,因?yàn)槲覀儸F(xiàn)在可以為每個(gè)用例提供包含相關(guān)切片的二進(jìn)制文件。事實(shí)上,如果我們需要,我們可以走得更遠(yuǎn),例如,在同一個(gè) xcframework 中捆綁一個(gè)包含 iOS 目標(biāo)的 UIKit? 接口的二進(jìn)制文件和一個(gè)包含 macOS 的 AppKit 接口的二進(jìn)制文件,然后讓 Xcode 基于期望的目標(biāo)架構(gòu)決定使用哪一個(gè)。
在 Swift 包中,那先能夠以 binaryTarget[8] 被包含進(jìn)項(xiàng)目的,能夠在包中被引入任意其他目標(biāo)。這相同的操作同樣適用于 frameworks。
命令行工具相關(guān)
由于 Swift 5.6 版本中引入了用于 Swift 包管理器的 可擴(kuò)展構(gòu)建工具[9] ,因此可以在構(gòu)建過程中的不同時(shí)間執(zhí)行命令。
這是 iOS 社區(qū)長期以來一直強(qiáng)烈要求的事情,例如格式化源代碼、代碼生成甚至收集公制代碼庫的指標(biāo)。Swift 5.6 中所有這些所謂的 插件[10] 最終都需要調(diào)用可執(zhí)行文件來執(zhí)行特定任務(wù)。這是二進(jìn)制文件再次在 Swift 包中參與的地方。
在大多數(shù)情況下,對于我們 iOS 開發(fā)人員來說,這些工具將來自同時(shí)支持 macOS 的不同架構(gòu)切片 —— Apple Silicon 的 arm64 架構(gòu)和 Intel Mac 的 x86_64 架構(gòu)。開發(fā)者工具如, SwiftLint[11] 或 SwiftGen[12] 正是這種案例。在這種情況下,可以使用包含可執(zhí)行文件(本地或遠(yuǎn)程)的 .zip 文件的路徑創(chuàng)建新的二進(jìn)制目標(biāo)。
注意可執(zhí)行文件必須在.zip文件的根目錄下,否則找不到。
Artifact Bundles
到目前為止,命令行工具所采用的方法僅適用于 macOS 架構(gòu)。但我們不能忘記,Linux 機(jī)器也支持 Swift 包。這意味著如果要同時(shí)支持 M1 macs (arm64?) 和 Linux arm64? 機(jī)器,上面的胖二進(jìn)制方法將不起作用 —— 請記住,二進(jìn)制不能包含具有相同架構(gòu)的多個(gè)切片。在這個(gè)階段可能有人會(huì)想,我們可以不只使用 xcframeworks 嗎?不,因?yàn)樗鼈冊?Linux 操作系統(tǒng)上不受支持!
Apple 已經(jīng)考慮到這一點(diǎn),除了引入 可擴(kuò)展構(gòu)建工具[13] 之外,Artifact Bundles[14] 和對二進(jìn)制目標(biāo)的其他改進(jìn)也作為 Swift 5.6 的一部分發(fā)布。
工件包(Artifact Bundles) 是包含 工件 的目錄。這些工件需要包含支持架構(gòu)的所有不同二進(jìn)制文件。二進(jìn)制文件和支持的架構(gòu)的路徑是使用清單文件 (info.json) 指定的,該文件位于 Artifact Bundle 目錄的根目錄中。你可以將此清單文件視為一個(gè)地圖或指南,以幫助 Swift 確定哪些可執(zhí)行文件可用于哪種架構(gòu)以及可以在哪里找到它們。
以 SwiftLint 為例
?SwiftLint[15] 在整個(gè)社區(qū)中被廣泛用作 Swift 代碼的靜態(tài)代碼分析工具。由于很多人都非??释屵@個(gè)插件在他們的 SwiftPM 項(xiàng)目中運(yùn)行,我認(rèn)為這將是一個(gè)很好的例子來展示我們?nèi)绾螌⒎职l(fā)的可執(zhí)行文件從他們的發(fā)布頁面變成一個(gè)與 macOS 架構(gòu)和 Linux arm64 兼容的工件包。
讓我們從下載兩個(gè)可執(zhí)行文件(macOS[16] 和 Linux[17])開始。
至此,bundle的結(jié)構(gòu)就可以創(chuàng)建好了。為此,創(chuàng)建一個(gè)名為 swiftlint.artifactbundle? 的目錄并在其根目錄添加一個(gè)空的 info.json:
mkdir swiftlint.artifactbundle
touch swiftlint.artifactbundle/info.json
現(xiàn)在可以使用 schemaVersion 填充清單文件,這可能會(huì)在未來版本的工件包和具有兩個(gè)變體的工件中發(fā)生變化,這將很快定義:
{
"schemaVersion": "1.0",
"artifacts": {
"swiftlint": {
"version": "0.47.0", # The version of SwiftLint being used
"type": "executable",
"variants": [
]
},
}
}
需要做的最后一件事是將二進(jìn)制文件添加到包中,然后將它們作為變體添加到 info.json? 文件中。讓我們首先創(chuàng)建目錄并將二進(jìn)制文件放入其中(macOS 的一個(gè)在 swiftlint-macos/swiftlint?,Linux 的一個(gè)在 swiftlint-linux/swiftlint)。
添加這些之后,可以在清單文件中變量:
{
"schemaVersion": "1.0",
"artifacts": {
"swiftlint": {
"version": "0.47.0", # The version of SwiftLint being used
"type": "executable",
"variants": [
{
"path": "swiftlint-macos/swiftlint",
"supportedTriples": ["x86_64-apple-macosx", "arm64-apple-macosx"]
},
{
"path": "swiftlint-linux/swiftlint",
"supportedTriples": ["x86_64-unknown-linux-gnu"]
},
]
},
}
}
為此,需要為每個(gè)變量指定二進(jìn)制文件的相對路徑(從工件包目錄的根目錄)和支持的三元組。如果您不熟悉 目標(biāo)三元組[18],它們是一種選擇構(gòu)建二進(jìn)制文件的架構(gòu)的方法。請注意,這不是 主機(jī)(構(gòu)建可執(zhí)行文件的機(jī)器)的體系結(jié)構(gòu),而是 目標(biāo) 機(jī)器(應(yīng)該運(yùn)行所述可執(zhí)行文件的機(jī)器)。
這些三元組具有以下格式: ----? 并非所有字段都是必需的,如果其中一個(gè)字段未知并且要使用默認(rèn)值,則可以省略或替換為 unknown 關(guān)鍵字。
可執(zhí)行文件的架構(gòu)切片可以通過運(yùn)行 file 找到,這將打印捆綁的任何切片的供應(yīng)商、系統(tǒng)和架構(gòu)。在這種情況下,為這兩個(gè)命令運(yùn)行它會(huì)顯示:
swiftlint-macos/swiftlint
swiftlint: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64]
swiftlint (for architecture x86_64): Mach-O 64-bit executable x86_64
swiftlint (for architecture arm64): Mach-O 64-bit executable arm64
swiftlint-linux/swiftlint
-> file swiftlint
swiftlint: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, with debug_info, not stripped
這帶來了上面顯示的 macOS 支持的兩個(gè)三元組(x86_64-apple-macosx、arm64-apple-macosx?)和 Linux 支持的一個(gè)三元組(x86_64-unknown-linux-gnu)。
與 XCFrameworks 類似,工件包也可以通過使用 binaryTarget 包含在 Swift 包中。
結(jié)論
簡而言之,我們可以總結(jié) 2022 年如何在 Swift 包中使用二進(jìn)制文件的最佳實(shí)踐,如下所示:
- 如果你需要為你的 iOS/macOS 項(xiàng)目添加預(yù)編譯庫或可執(zhí)行文件,您應(yīng)該使用XCFramework,并為每個(gè)用例(iOS 設(shè)備、macOS 設(shè)備和 iOS 模擬器)包含單獨(dú)的二進(jìn)制文件。
- 如果你需要?jiǎng)?chuàng)建一個(gè)插件并運(yùn)行一個(gè)可執(zhí)行文件,你應(yīng)該將其嵌入為一個(gè)工件包,其中包含適用于不同支持架構(gòu)的二進(jìn)制文件。
參考資料
[1] Carthage: https://github.com/Carthage/Carthage。
[2] CocoaPods: https://github.com/CocoaPods/CocoaPods。
[3] Pods: https://cocoapods.org/。
[4] cocoapods-binary: https://github.com/leavez/cocoapods-binary。
[5] Google: https://developers.google.com/cast/docs/ios_sender#manual_setup。
[6] XCFrameworks: https://developer.apple.com/videos/play/wwdc2019/416/。
[7] XCFrameworks: https://help.apple.com/xcode/mac/11.4/#/dev6f6ac218b。
[8] binaryTarget: https://developer.apple.com/documentation/swift_packages/distributing_binary_frameworks_as_swift_packages。
[9] Extensible Build Tools: https://github.com/apple/swift-evolution/blob/main/proposals/0303-swiftpm-extensible-build-tools.md。
[10] Plugins: https://github.com/apple/swift-evolution/blob/main/proposals/0303-swiftpm-exte""nsible-build-tools.md#plugin-api。
[11] SwiftLint: https://github.com/realm/SwiftLint。
[12] SwiftGen: https://github.com/SwiftGen/SwiftGen。
[13] Extensible Build Tools: https://github.com/apple/swift-evolution/blob/main/proposals/0303-swiftpm-extensible-build-tools.md。
[14] Artifact Bundles: https://github.com/apple/swift-evolution/blob/main/proposals/0305-swiftpm-binary-target-improvements.md。
[15] SwiftLint: https://github.com/realm/SwiftLint。
[16] macOS: https://github.com/realm/SwiftLint/releases/download/0.47.0/portable_swiftlint.zip。
[17] Linux: https://github.com/realm/SwiftLint/releases/download/0.47.0/swiftlint_linux.zip。
[18] target-triples: https://clang.llvm.org/docs/CrossCompilation.html#target-triples。