WWDC 2018:开始了解 Swift Package Manager

SwiftPM

本文同步发表在老司机x知识小集xSwiftGG WWDC 18 专题,标题:WWDC 2018:细说 Swift 包管理工具 (Swift Package Manager)
WWDC 2018 Session 411: Getting to Know Swift Package Manager

目前,在 macOS/iOS 开发中,我们通常使用 CocoaPodsCarthage非官方工具来管理项目工程中对第三方开源库的依赖。

Swift Package Manager(Swift 包管理器,一般简称 SwiftPM 或者 SPM)是苹果官方提供的一个用于管理源代码分发的工具,旨在使分享代码和复用其他人的代码变得更加容易。该工具可以帮助我们编译和链接 Swift packages(包),管理依赖关系、版本控制,以及支持灵活分发和协作(公开、私有、团队共享)等。

SwiftPM 于 2016 年随 Swift 3.0 一起发布,已经两年多了,但 WWDC 2018 专门开了一个 Session 用来介绍如何使用它,我们一起来了解一下吧。

为什么要有 SwiftPM ?

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 版本中,你可以很方便地使用它。

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 快速入门指引》这篇文章的翻译,我们这里不再赘述。

如何使用 SwiftPM ?

如上文所述,Swift Package Manager 提供了一个完整的系统用于构建 libraries(库)和 executables(可执行文件),以及可以跨不同 packages(包)共享代码,下面我们来介绍一下如何使用 SwiftPM。

SwiftPM 基本命令

  • swift build: 用于编译 package

  • swift run: 用于编译并运行一个可执行文件,该命令在 Swift 4 中新增加,详见这个提案,它相当于:

    1
    2
    $ swift build
    $ .build/debug/myexecutable
  • swift test: 用于运行 package 中的单元测试

  • swift package: 在 package 中进行各种除编译/运行/测试之外的操作,如创建、编辑、更新、重置、修改编译选项/路径等

此外,你可以在命令行中执行 swift package --version 查看当前 SwiftPM 的版本:

1
2
$ swift package --version
Apple Swift Package Manager - Swift 4.1.0 (swiftpm-14050)

也可以执行 swift package --help 查看关于命令的更多帮助。

关于上述 4 个基本命令的使用和更多信息,可以查阅官方使用示例文档

如何创建 Package

  • 创建一个 executable package
1
2
3
4
5
$ mkdir helloword    // 创建 helloword 文件夹
$ cd helloword // 进入 helloword 文件夹
$ swift package init --type executable // 在当前文件下初始化 package 为可执行项目
$ swift run // 编译并执行
Hello, world!

执行 swift package init --type executable 命令后,在 helloword 文件夹中会自动生成如下目录和文件:

SwiftPM

此外,你也可以在当前文件夹下执行 swift package generate-xcodeproj 命令生成一个 Xcode 工程,例如:

SwiftPM

  • 创建一个 library package
1
2
3
4
5
$ mkdir MyPackage
$ cd MyPackage
$ swift package init # or swift package init --type library
$ swift build
$ swift test

添加 Package 依赖

如果你的 package 需要依赖于其它 package,你只需要在 Package.swift 配置清单文件中添加依赖的 GitHub URL 和对应的版本号(关于版本号的规则,下文会详细讲)即可:

1
2
3
4
5
6
7
8
9
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 上对外发布:

1
2
3
4
5
6
$ 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 的内部结构

SwiftPM

一个 package 主要由如下三个部分组成,它们的概念我们前面已经介绍过:

  • Dependencies / 依赖

  • Targets / 目标

  • Products / 产品

一个常见的 Package.swift 配置文件内容大致如下:

SwiftPM

其中,targets 是 package 的基本组成部分,它描述了如何将一组源文件构建成模块或测试用例,一个 target 可以依赖于当前 package 中的其它 target,也可以依赖于声明为依赖关系的其他 package 所导出的 product(产品)。

例如上述 package 中声明依赖了一个外部 package,名字为 DeckOfPlayingCards,其版本为大于等于 3.0.0,且 libdealer target 依赖于该外部产品 DeckOfPlayingCards,而 dealer 则依赖于自己 package 中的 libdealer,最后 dealerTests 依赖于前面这两个 targets,用于对 package 相关功能进行单元测试。

另外,在该文件中定义了两个 products,分别为 libdealerdealer,它们与 targets 相对应,一般由一个或多个 targets 组成,并最终将被分别构建成 library(库)和 executable(可执行文件)。

SwiftPM 的设计理念

Swift Package Manager 是 Swift 开源项目中的一部分,遵循 Swift 的理念:

  1. Safe: 安全,隔离的构建环境

  2. Fast: 快速,可扩展到大型依赖关系图

  3. Expressive: 有表现力的,使用 Swift 语言定义 manifest 配置文件的格式

SwiftPM

通过 SwiftPM 构建一个 package 主要包括如下几个步骤:

  • Configuration

如上所述,SwiftPM 使用 Swift 语言来编写其配置清单的格式,容易理解,无需额外的学习成本,其遵循Swift API 设计准则,可以得到现有的 Swift 工具的支持。

苹果推荐在 Package.swift 中优先使用明确的语义,而少用变量替换和拼接,方便自己维护和用户更直观地阅读和理解:

SwiftPM

SwfitPM 会自动包含磁盘上当前 package 中 Sources 目录中的源文件,且 Sources 目录下的各子文件夹中的代码会自动与同名的 target 关联,而不需要在清单文件中显式声明:

SwiftPM

此外,它也支持编译其他语言,如 C/C++/Objective-C 等,但是目前不支持将这些语言与 Swift 混合编译放在同一个 target 中,需要分别放在不同的 target。

  • Dependencies & Versioning

SwiftPM 采用语义化版本来控制依赖的版本,规则如下:

版本格式:主版本号.次版本号.修订号,版本号递增规则为:

  1. 主版本号:当你做了不兼容的 API 重大修改,
  2. 次版本号:当你做了向下兼容的功能性新增,
  3. 修订号:当你做了向下兼容的问题修正。

例如:.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

此外,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 语言版本,便于编译,它是一个列表,可以接受多个版本号。

SwiftPM

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: 可扩展的编译工具,自动根据源文件的代码注释和相关配置,生成二进制文件及其使用文档

SwiftPM

  • 搜索,管理和校验信任 Package

Idea: Package 内容的校验,防止被篡改

Idea: 跨平台的沙箱隔离(Cross-Platform Sandboxing)

Idea: 支持 Fork 操作

Idea: 支持创建 Package 索引,提升搜索速度

更多关于 SwiftPM 的未来新特性,可以在 swift-evolution 中找到更加详细的描述,你也可以在上面提交 proposals 贡献你的想法。

SwiftPM

SwiftPM 相关开源进展

你可以通过以下几个渠道关注或参与 Swift 和 SwiftPM 等项目的进展:

此外,随着 Swift 的发展,SwiftPM 在服务端(Server-Side Swift)开发和命令行工具(Command-Line Utilities)开发中也有越来越广泛的应用。

Swift Evolution 翻译计划

去年 Swift 4 发布时,我们整理翻译了 swift-evolution 中新增的 Package Manager 相关的部分提案(proposals),详见:

后续我们会继续翻译 swift-evolution 中的其他新提案,欢迎和我们一起完善。

令人遗憾的是…

讲真,当看到 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 发布以后就能支持了吧?让我们一起期待吧。

参考链接