WWDC 2018 Session 411: Getting to Know Swift Package Manager
檢視更多 WWDC 18 相關文章請前往 老司機x知識小集xSwiftGG WWDC 18 專題目錄
作者:KANGZUBIN
目前,在 macOS/iOS 開發中,我們通常使用 CocoaPods 或 Carthage 等非官方工具來管理專案工程中對第三方開源庫的依賴。
Swift Package Manager(Swift 包管理器,一般簡稱 SwiftPM 或者 SPM)是蘋果官方提供的一個用於管理原始碼分發的工具,旨在使分享程式碼和複用其他人的程式碼變得更加容易。該工具可以幫助我們編譯和連結 Swift packages(包),管理依賴關係、版本控制,以及支援靈活分發和協作(公開、私有、團隊共享)等。
SwiftPM 於 2016 年隨 Swift 3.0 一起釋出,已經兩年多了,今年 WWDC 2018 專門開了一個 Session 用來介紹如何使用它,我們一起來了解一下吧。
0. 為什麼要有 SwiftPM ?
目前已經有很多優秀包管理器用於分享和複用程式碼,對於為什麼要為 Swift 專門開發一個新的包管理工具(Why a new package manager for Swift?),蘋果的工程師在該 Session 中主要總結了如下幾個理由:
- 一個 Swift 的跨平臺構建系統
首先,Swift 是一個跨平臺開發語言,所以我們需要一個強大的跨平臺工具用於構建 Swift 程式碼,它可以用一致的方式輕鬆配置程式碼,然後在 Swift 所支援的所有平臺上執行。SwiftPM 包含其自己的完整構建系統,使你能夠僅在一個工具中完成程式碼的編譯、測試和執行等。
- 權威規範的包管理工具
蘋果希望在 Swift 專案中提供一個官方權威統一規範的包管理器,通過定義一個分發 libraries(庫)的通用標準,讓我們儘可能方便地在 Swift 生態系統中與他人分享 Swift libraries(庫)。
- 核心庫之外的程式碼複用
在開發過程中,核心標準庫有時候不能滿足我們的需求,我們可能會想擴充套件它,但為了保證核心庫 API 的簡潔,不可能隨意往裡新增內容。因此,有一個強大的包管理器就可以很容易地將這些擴充套件作為 package 來分發和共享,而不必將它們放到核心庫中,而那些好的擴充套件可以隨著時間的推移持續地獲得社群的支援並且變得越來越標準化。
- 充分利用 Swift 的優勢
SwiftPM 本身是用 Swift 編寫的,它可以充分利用 Swift 的強大功能和設計理念,通過與 Swift 語言和核心庫專案密切合作,可以建立出更加出色的包管理器功能。
- 包含在 Swift 工具鏈和 Xcode 中
SwiftPM 是 Swift 開源專案中的一部分,它被包含在每個 Swift 工具鏈中,同時它也包含在 Xcode 的每個 Release 版本中,你可以很方便地使用它。
1. SwiftPM 基本概念
- 什麼是 Packages?
一個 package(包)由 Swift 原始碼檔案和一個清單檔案組成。這個清單檔案被稱為 Package.swift
,它使用 PackageDescription
模組來定義包的名稱和內容。一個包通常有一個或者多個 targets(目標),每個 target 指定一個 product(產品)並宣告一個或者多個 dependencies(依賴)。
- 什麼是 Products?
在 package(包)中的每一個 target(目標)最終可能構建成一個 library(庫)或者一個 executable(可執行檔案)作為其 product(產品)。庫是包含可以被其他 Swift 程式碼引用的模組,可執行檔案是一段可以被作業系統執行的程式。
對於 SwiftPM 的一些基本概念,例如:Modules、Packages、Products、Dependencies、Targets 等,在 Swift.org 官網已經有非常詳細的描述和定義,另外,也可以參見《Swift Package Manager 快速入門指引》這篇文章的翻譯,我們這裡不再贅述。
2. 如何使用 SwiftPM ?
如上文所述,Swift Package Manager 提供了一個完整的系統用於構建 libraries(庫)和 executables(可執行檔案),以及可以跨不同 packages(包)共享程式碼,下面我們來介紹一下如何使用 SwiftPM。
SwiftPM 基本命令
-
swift build: 用於編譯 package
-
swift run: 用於編譯並執行一個可執行檔案,該命令是在 Swift 4 中新增加的,詳見這個提案,它相當於:
$ swift build
$ .build/debug/myexecutable
複製程式碼
-
swift test: 用於執行 package 中的單元測試
-
swift package: 在 package 中進行各種除編譯/執行/測試之外的操作,如建立、編輯、更新、重置、修改編譯選項/路徑等
此外,你可以在命令列中執行 swift package --version
檢視當前 SwiftPM 的版本:
$ swift package --version
Apple Swift Package Manager - Swift 4.1.0 (swiftpm-14050)
複製程式碼
也可以執行 swift package --help
檢視關於命令的更多幫助。
關於上述 4 個基本命令的使用和更多資訊,可以查閱官方使用示例和文件。
如何建立 Package
- 建立一個 executable package
$ mkdir helloword // 建立 helloword 資料夾
$ cd helloword // 進入 helloword 資料夾
$ swift package init --type executable // 在當前檔案下初始化 package 為可執行專案
$ swift run // 編譯並執行
Hello, world!
複製程式碼
執行 swift package init --type executable
命令後,在 helloword
資料夾中會自動生成如下目錄和檔案:
此外,你也可以在當前資料夾下執行 swift package generate-xcodeproj
命令生成一個 Xcode 工程,例如:
- 建立一個 library package
$ mkdir MyPackage
$ cd MyPackage
$ swift package init # or swift package init --type library
$ swift build
$ swift test
複製程式碼
新增 Package 依賴
如果你的 package 需要依賴於其它 package,你只需要在 Package.swift
配置清單檔案中新增依賴的 GitHub URL 和對應的版本號(關於版本號的規則,下文會詳細講)即可:
import PackageDescription
let package = Package(
name: "MyPackage",
dependencies: [
// 新增依賴包:PlayingCard
.Package(url: "https://github.com/apple/example-package-playingcard.git", from: "3.0.0"),
]
)
複製程式碼
接下來你就可以在當前 package 中的任何原始檔中 import PlayingCard
,並呼叫它的公開 APIs。
同時,你也可以通過修改 Package.swift
檔案中相關依賴的版本號,並執行 swift package update
命令來更新依賴包。
釋出一個 Package
一個 package 通常就是一個的 git 倉庫,並通過標籤語義來表示版本(versioned tags)
因此,如果要對外釋出一個 package,你只要在你的 git 倉庫中新增一個用於表示版本號的標籤(如 tag 1.0.0),即可提交到 GitHub 上對外發布:
$ git init
$ git add .
$ git remote add origin [github-URL]
$ git commit -m "Initial Commit"
$ git tag 1.0.0
$ git push origin master --tags
複製程式碼
執行上述命令後,其他 package 就可以通過 GitHub URL 依賴此 package 的當前版本(1.0.0)。
你也可以參考蘋果釋出的 package 示例:example-package-fisheryates
Package 的內部結構
一個 package 主要由如下三個部分組成,它們的概念我們前面已經介紹過:
-
Dependencies / 依賴
-
Targets / 目標
-
Products / 產品
一個常見的 Package.swift
配置檔案內容大致如下:
其中,targets 是 package 的基本組成部分,它描述瞭如何將一組原始檔構建成模組或測試用例,一個 target 可以依賴於當前 package 中的其它 target,也可以依賴於宣告為依賴關係的其他 package 所匯出的 product(產品)。
例如上述 package 中宣告依賴了一個外部 package,名字為 DeckOfPlayingCards
,其版本為大於等於 3.0.0
,且 libdealer
target 依賴於該外部產品 DeckOfPlayingCards
,而 dealer
則依賴於自己 package 中的 libdealer
,最後 dealerTests
依賴於前面這兩個 targets,用於對 package 相關功能進行單元測試。
另外,在該檔案中定義了兩個 products,分別為 libdealer
和 dealer
,它們與 targets 相對應,一般由一個或多個 targets 組成,並最終將被分別構建成 library(庫)和 executable(可執行檔案)。
3. SwiftPM 的設計理念
Swift Package Manager 是 Swift 開源專案中的一部分,遵循 Swift 的理念:
-
Safe: 安全,隔離的構建環境
-
Fast: 快速,可擴充套件到大型依賴關係圖
-
Expressive: 有表現力的,使用 Swift 語言定義 manifest 配置檔案的格式
通過 SwiftPM 構建一個 package 主要包括如下幾個步驟:
- Configuration
如上所述,SwiftPM 使用 Swift 語言來編寫其配置清單的格式,容易理解,無需額外的學習成本,其遵循Swift API 設計準則,可以得到現有的 Swift 工具的支援。
蘋果推薦在 Package.swift
中優先使用明確的語義,而少用變數替換和拼接,方便自己維護和使用者更直觀地閱讀和理解:
SwfitPM 會自動包含磁碟上當前 package 中 Sources 目錄中的原始檔,且 Sources 目錄下的各子資料夾中的程式碼會自動與同名的 target 關聯,而不需要在清單檔案中顯式宣告:
此外,它也支援編譯其他語言,如 C/C++/Objective-C 等,但是目前不支援將這些語言與 Swift 混合編譯放在同一個 target 中,需要分別放在不同的 target。
- Dependencies & Versioning
SwiftPM 採用語義化版本來控制依賴的版本,規則如下:
版本格式:主版本號.次版本號.修訂號,版本號遞增規則為:
- 主版本號:當你做了不相容的 API 重大修改,
- 次版本號:當你做了向下相容的功能性新增,
- 修訂號:當你做了向下相容的問題修正。
例如:.exact("0.2.1")
指定某一個具體的版本,from: "3.0.0"
指定獲取大於等於 3.0.0
(但小於 4.0.0
)的最新版本,tag 3.1.4
指定獲取某一標籤,upToNextMinor(from: "1.5.8")
獲取指定版本到下一個次版本號(即 1.5.8
~ 1.6.0
)之間的最新版本。
此外,packge 之間的依賴是可以傳遞的(遞迴),比如 A 依賴了 B,B 依賴了 C,那麼 A 當然也自動會依賴於 C:
此外,SwiftPM 同樣使用一個 Package.resolved
檔案用於記錄已經解析安裝的 packages 版本,類似於 CocoaPods 中的 Podfile.lock
或 Carthage 中的 Cartfile.resolved
。
它可以用於共享可靠的編譯結果,且易於進行新版本更新,但僅適用於頂層(top-level)的 package,例如上圖中的 dealer
。
- Building
SwiftPM 使用 llbuild
作為其底層編譯引擎,提供快速和正確的增量編譯,它也被 Xcode 新的編譯建系統使用,是 Swift 開源專案的一部分。
SwiftPM 進行構建 package 的編譯環境是在一個沙盒中被隔離獨立的,無法隨意執行命令或 shell 指令碼,因此保證了其安全性。同時也支援基於 XE.framework 單元測試,併發測試,或者指定測試某些場景(Test Filtering)等。
- Workflow Features
SwiftPM 提供了非常靈活的工作流程,你可以讓你的 package 依賴於某一處於編輯模式(Edit Model)的 package(把某一遠端 package 下載到本地臨時目錄進行編輯),方便你進行除錯。你也可以指定依賴 package 的某一分支或者標籤,進行特定的功能測試。同時,它也允許你依賴本地的 Package(Local Package Dependencies)。
- Tools Evolution
每個 Package 中的 API 可以隨 Swift 的新版本進行更新,但以前的 API 仍然可用,SwiftPM 允許在不更新清單的情況下使用新的 Swift 工具。
因為我們可以在 Package.swift
清單檔案中指定工具版本和語言版本。如下圖所示,第一行註釋用於指定 Swift 工具最低版本號,第二行用於指定該 package 中程式碼使用 Swift 語言版本,便於編譯,它是一個列表,可以接受多個版本號。
4. SwiftPM 未來新特性概覽
本節簡要介紹一下 Session 中提到的 SwiftPM 即將支援的一些新特性。
- 與其他工具更好地整合
SwiftPM 官方的提供庫 libSwiftPM
現在已經可用,蘋果鼓勵開發人員可以基於這個庫為 SwiftPM 開發一些擴充套件工具,如:
Idea: Machine-Editable Package.swift,基於 libSyntax
庫,當使用者在編輯 Package.swift
時,可以自動根據使用者的輸入,預生成相應配置程式碼。
- 釋出和部署相關
Idea: 標籤和釋出支援,目前 SwiftPM 的 package 依賴於 git 的標籤來發布版本,流程比較繁瑣,後續工具本身將新增新功能來簡化並自動執行此過程。
Idea: 自動進行語義版本增加
Idea: 部署自動化
- 支援複雜的 Package 配置
Idea: 支援圖片等資原始檔(Resources)
Idea: 支援編譯設定(Build Settings)
Idea: 可擴充套件的編譯工具,自動根據原始檔的程式碼註釋和相關配置,生成二進位制檔案及其使用文件
- 搜尋,管理和校驗信任 Package
Idea: Package 內容的校驗,防止被篡改
Idea: 跨平臺的沙箱隔離(Cross-Platform Sandboxing)
Idea: 支援 Fork 操作
Idea: 支援建立 Package 索引,提升搜尋速度
更多關於 SwiftPM 的未來新特性,可以在 swift-evolution 中找到更加詳細的描述,你也可以在上面提交 proposals 貢獻你的想法。
5. SwiftPM 相關開源進展
你可以通過以下幾個渠道關注或參與 Swift 和 SwiftPM 等專案的進展:
- SwiftPM 專案官網
https://swift.org/package-manager/
- Swift 版本演化
https://github.com/apple/swift-evolution
- Swift 論壇
https://forums.swift.org/c/development/SwiftPM
- Swift Bug 跟蹤
https://bugs.swift.org
- Swift 持續整合:@swift-ci
https://ci.swift.org
- Trunk Snapshots / 快照
https://swift.org/download/#snapshots
- GitHub 社群貢獻
https://github.com/apple/swift-package-manager
此外,隨著 Swift 的發展,SwiftPM 在服務端(Server-Side Swift)開發和命令列工具(Command-Line Utilities)開發中也有越來越廣泛的應用。
6. Swift Evolution 翻譯計劃
去年 Swift 4 釋出時,我們整理翻譯了 swift-evolution 中新增的 Package Manager 相關的部分提案(proposals),詳見:
後續我們會繼續翻譯 swift-evolution 中的其他新提案,歡迎和我們一起完善。
7. 令人遺憾的是...
講真,當看到 WWDC 2018 時間表中這個 Swift Package Manager 相關的 Session 時,我是很興奮,感覺它應該可以支援 iOS 開發了吧?
但是,等到大會結束時,我看到 GitHub 上 swift-package-manager 專案的 README 描述中,仍然保留著這句話:
Note that at this time the Package Manager has no support for iOS, watchOS, or tvOS platforms.
很遺憾,目前 Swift Package Manager 仍然僅支援在 macOS、Linux 開發中使用,並不支援 iOS/watchOS/tvOS 等平臺,也許在 Swift 5 釋出以後就能支援了吧?讓我們一起期待吧。