Swift Package Manager - Swift自带的包管理器
WWDC19 为 Swift 带来了新的活力 - Swift Package Manager 将促使 Swift 向其他领域的发展迈出重要的一步。
为了能够更方便地通过 Swift 创建可执行文件及第三方库,Apple 提供了 Swift Package Manager 来帮助开发人员管理项目,以便复用代码及简化管理流程。
相关概念
Package
Package 包含一系列的 Swift 源文件和一个名为Package.swift
的配置文件。配置文件中定义了Package 的名称和内容。一个 Package 可以产生多个 target,而每个 target 唯一对应一个 product 及一个或以上的 dependency。
Target 是 Package 产生的目标,一个 Package 可以有多个目标。每个 Target 都产生一个输出,这个输出可以是空的、可执行文件、库、系统模块,并且每个 target 都可以是基于其他的库进行开发的,这些库就称为这个target 的依赖(dependency)。
Module
Swift 将 Package 中的文件以 module(模块)的形式进行管理,每一个 module 都规定了一个命名空间(namespace),并且通过访问控制符,可以控制 module 内部的代码是否可以被 module 外的代码所访问。
每一个工程都可以将它需要用到的代码全部包含到一个 module 中,也可以将其他的 module 导入(import)进来,作为自己的依赖(dependency)。注意 target 的依赖和工程的依赖稍有不同,所有 target 的依赖构成的集合才是工程的依赖。
通过将能够解决某一特定类型的问题的代码封装成一个独立的模块,可以使代码复用到其他场景中。例如,用于解析JSON数据的模块可以用到所有需要与网络数据打交道的项目中,这样就不需要重新自己写相应功能的代码了。对这类代码的要求是必须独立于项目之外,具有类似于函数的特性。
SPM 允许我们从本地或者网络上获取到我们所需要的其他第三方 module。
Library
Library 类似于开发中的库,即工程本身不产生可执行文件,而是作用一个通用的功能模块,可以被导入到其他项目中发挥作用。
Dependency
如果想要能够复用代码,就需要指定自己的工程依赖于哪些外部的代码,因为自己的工程能否正常运行取决于这些外部代码是否正常工作,因此这些外部代码也被称为依赖(dependency)。
简单使用方法
下面简单介绍一下SPM的使用方法。
创建项目
在需要创建项目的目录中执行swift package init
以创建一个新的 Swift 项目。
$ mkdir testProgram
$ cd testProgram
$ swift package init (--type library/executable/empty/system module)
type可以有四种类型:
- library: 创建库
- executable: 创建可执行文件
- empty:创建空项目
- system module:创建系统模块项目
默认创建的是library。
另外,在创建好项目后,为了能够充分发挥Xcode强大的功能,还可以生成 Xcode project 以便在 Xcode 中编辑、调试代码。cd 到项目目录后,执行swift package generate-xcodeproj
就可以创建对应的 Xcode project 了。
目前只能在 Xcode 中实现代码编辑,暂时没有找到能够充分发挥 Xcode 功能的办法,编译和运行都需要回到 Terminal 进行。
添加外部模块
如前所述,Package.swift
是项目的配置文件,模版代码如下。
// swift-tools-version:4.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "testProgram",
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
// A package can produce multiple executables and libraries.
.library(
name: "testProgram",
targets: ["testProgram"]),
.executable(
name: "MainExecutable",
targets: ["MainExecutable"])
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite, and of course an executable.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "testProgram",
dependencies: []),
.testTarget(
name: "testProgramTests",
dependencies: ["testProgram"]),
]
)
该模版代码创建了一个Package实例,并通过构造参数来指定项目的name、product、target和dependency。各字段作用如下:
- name:指定项目名称
- products:指定项目生成的东西,可以是library或者executable,同一个项目可以生成多个library或executable。
- dependencies: 指定项目所使用的依赖库及其URL、版本等信息。
- targets:指定项目生成的目标,
若要添加开源代码,在.dependency中添加:
.package(url: "open source url", from: "version number")
若要添加本地依赖,在.dependency中添加:
.package(path:"local path")
可以添加多个依赖,并且用上述类似的方法还可以创建多个products和targets。
然后在产生的targets中,指定对应的dependency的名称就可以了。
发布library
使用Git将自己创建好的library发布到托管仓库中。
$ git init // create a new git project
$ git add . // add all files to the stack
$ git remote add origin [github-URL] // add a remote origin in the remote repository
$ git commit -m "Initial Commit" // commit all files in the stack to local repository
$ git tag 1.0.0 // tag the branch
$ git push origin master --tags // push local repository to remote repository
tag标记的版本号就是其他项目将本项目作为依赖时引用的版本号(from)。对于本地依赖,虽然可以借助git的本地仓库回溯功能进行代码管理,但是在Swift Package Manager中暂时不支持引用本地依赖的版本号。
更新依赖包
编辑Package.swift
中依赖包的版本信息,然后执行swift package update
即可更新需要的依赖包。
创建模块
只需要将Package.swift
中的products添加.library就可以创建模块了。每一个Package可以产生多个library,不同的library在Sources目录下以不同名称的目录呈现。
如果生成的library之间有相互关联,则需要在完成一个library的编码工作后,先执行swift build
对已有的library进行编译,然后再进行下一个library的编码工作。这有助于以模块化的方式完成library的创建。
注意:对于使用Git开源的代码,需要打上git tag,别人才能够导入对应版本的代码。同样地,对于本地依赖,最好也加上git tag,但是应该不是必须的。
实例——结合使用Swift for TensorFlow和Swift Package Manager
为了能够同时使用Swift for TensorFlow和Swift Package Manager进行Swift机器学习项目的管理(个人认为这是比较通用的方法),需要先指定swift路径为SFT的路径,然后使用SPM对项目进行编译、运行等处理。下面macOS上的SFT和SPM为例介绍如何结合使用两者。
- 指定swift路径
在$PATH
中指定swift的路径。由于使用了Swift for TensorFLow,因此需要到GitHub仓库中下载对应的swift toolchain,链接在这里。该swift toolchain中包含了完整的swift编译器,与Xcode自带的toolchain相比多了能够使用TensorFlow的功能。
如果在Linux下进行开发,还需要事先配置好swift的环境,具体教程见这里。
- 创建model对应的library
由于机器学习模型是比较独立的部分,因此最好将其作为一个独立的module,然后将其导入到其他项目中去。接下来要做的创建ML model对应的library。
由于例子中MLModel没有使用到第三方库,因此配置文件不需要修改。在./Sources/name
中就包含了一个本地module,修改代码为:
import TensorFlow
struct MLPClassifier {
var w1 = Tensor<Float>(shape: [2, 4], repeating: 0.1)
var w2 = Tensor<Float>(shape: [4, 1], scalars: [0.4, -0.5, -0.5, 0.4])
var b1 = Tensor<Float>([0.2, -0.3, -0.3, 0.2])
var b2 = Tensor<Float>([[0.4]])
func prediction(`for` x: Tensor<Float>) -> Tensor<Float> {
let o1 = tanh(matmul(x, w1) + b1)
return tanh(matmul(o1, w2) + b2)
}
}
这里用一个简单的MLP模型作为例子进行展示,自定义了一个MLPClassifierd的类,定义了第一层及第二层的权重和bias,然后使用tanh作为activation function,得到“网络”输出的结果。
通过上述代码就完成了ML model的创建,可以运行swift build看看有没有问题。
为了加速模型的计算,会有warning说没有用optimization编译。对于小网络来说影响不大,但如果网络结构比较复杂的时候可能会导致训练过慢。暂时好像没有什么办法,唯一想到的解决方案是在release模式下编译,即
swift build -c release
,release模式默认使用-O进行编译。
- 创建swift项目
接下来创建使用前面建立好的module的swift项目。新建一个executable的项目,并在Package.swift中添加本地依赖MLModel和用于测试的第三方库SwiftyJSON。
测试代码如下:
import TensorFlow
import MLModel
let input = Tensor<Float>([[0.2, 0.8]])
let classifier = MLPClassifier()
let prediction = classifier.prediction(for: input)
print(prediction)
⚠️:SwiftyJSON要求toolchain版本在4以上,而Swift for TensorFlow的toolchain版本为3,因此暂时没有办法同时使用这两个框架。
- 编译运行
尝试进行编译运行。可以分别执行编译和运行两个步骤,也可以直接运行观察代码运行结果。
编译项目:
swift build
默认的编译环境是debug
,可通过-c release
的标记,将编译环境切换到release
。debug模式默认不开启-O优化,因此为了使Swift for TensorFlow获得最佳性能,建议先在release模式下编译项目,再到指定目录执行link生成的可执行文件。
运行项目:
swift run (executable name)
也可以找到build生成的可执行文件,然后直接执行。
如果项目中存在多个可执行文件,需要在
swift run
命令后增加对应的可执行文件的名称。利用这一特性可以将多个独立的工程汇总在一个package下面,便于管理,但是一个不好的地方是如果修改了一个地方就必须把当前package下的所有swift源文件编译一遍。目前的解决方法是当package规模扩大后,将其中一部分相对独立的功能打包成新的swift package,然后导入进来,这样就不用重新编译导入进来的源码了。
本文为原创文章,转载请注明。