如何使用Swift Package Manager

Swift Package Manager 是苹果推出的用于创建使用swift的库和可执行程序的工具。

SwiftPM有什么作用?

能够通过命令快速创建library或者可执行的swift程序,能够跨平台使用,能够使开发出来的项目能够在不同平台上运行。

SwiftPM有哪些局限?

1、目前只能用来写跨平台的项目,如swift服务端开发,现在的Vapor、Perfect等服务端的web框架均使用SwiftPM来构建和管理依赖。
2、iOS和MacOS目前还未支持,但是后续一定会支持,现在只需要耐心的等待。

注意:本文适用于Swift 4.1.0 版本,后续会有更新,如果里面的demo不能正常运行,请检查版本。

那么,让我门开始吧

  • 创建一个Package

1、创建文件夹,并进入文件夹

$ mkdir Hello
$ cd Hello

2、初始化一个名为Hello的package

$ swift package init

会生成以下的文件结构

Hello项目的目录结构

3、使用命令行编译项目

$ swift build
Compile Swift Module 'Hello' (1 sources)

使用命令行运行test

$ swift test
Compile Swift Module 'HelloTests' (2 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/HelloPackageTests.xctest/Contents/MacOS/HelloPackageTests
Test Suite 'All tests' started at 2018-08-28 11:23:16.755
Test Suite 'HelloPackageTests.xctest' started at 2018-08-28 11:23:16.755
Test Suite 'HelloTests' started at 2018-08-28 11:23:16.755
Test Case '-[HelloTests.HelloTests testExample]' started.
Test Case '-[HelloTests.HelloTests testExample]' passed (0.264 seconds).
Test Suite 'HelloTests' passed at 2018-08-28 11:23:17.019.
     Executed 1 test, with 0 failures (0 unexpected) in 0.264 (0.264) seconds
Test Suite 'HelloPackageTests.xctest' passed at 2018-08-28 11:23:17.019.
     Executed 1 test, with 0 failures (0 unexpected) in 0.264 (0.264) seconds
Test Suite 'All tests' passed at 2018-08-28 11:23:17.019.
     Executed 1 test, with 0 failures (0 unexpected) in 0.264 (0.264) seconds

  • 创建一个可执行项目

1、创建文件夹,并进入文件夹

$ mkdir HelloExcutable
$ cd HelloExcutable/

2、初始化一个名为Hello的可执行文件

$ swift package init --type executable

会生成以下的文件结构

HelloExcutable的目录结构

3、运行HelloExcutable

$ swift run HelloExcutable
Compile Swift Module 'HelloExcutable' (1 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/HelloExcutable
Hello, world!

4、Build HelloExcutable

$ swift build

会在当前目录创建一个.build的文件夹,里面是编译后的内容。

编译后的目录结构

可以看到在x86_64-apple-macosx10.10/debug文件夹里有一个HelloExcutable的可执行文件。

5、直接运行HelloExcutable

$ .build/x86_64-apple-macosx10.10/debug/HelloExcutable

6、多个xxx.swift的情况:
在HelloExcutable(和Main.swift同级)的目录下创建名为Greeter.swift的文件,在里面插入代码:

func sayHello(name: String) {
    print("Hello, \(name)!")
}

修改Main.swift:

if CommandLine.arguments.count != 2 {
    print("Usage: hello NAME")
} else {
    let name = CommandLine.arguments[1]
    sayHello(name: name)
}

7、运行来查看效果

$ swift run HelloExcutable `whoami`
Compile Swift Module 'HelloExcutable' (2 sources)
Linking /Users/leacode/Documents/Swift/HelloExcutable/.build/x86_64-apple-macosx10.10/debug/HelloExcutable
Hello, leacode!

会获得命令行的结果,并打印出来

实际项目种使用

  • 创建项目

执行命令

$ swift package init --help
OVERVIEW: Initialize a new package

OPTIONS:
  --type   empty|library|executable|system-module

可以看到目前SwiftPM支持四种类型,指定不同的type可以创建不同类型的项目。由于swift支持不同的平台,所以创建项目的时候并没有生成xcode文件,如果是在非Mac平台开发,可以使用其他IDE进行开发,如果在Mac上开发就会方便很多,可以使用以下命令创建xcodeproj文件:

$ swift package generate-xcodeproj
  • 编译项目

执行命令

$ swift build --help
OVERVIEW: Build sources into binary products

USAGE: swift build [options]

OPTIONS:
  --build-path            Specify build/cache directory [default: ./.build]
  --build-tests           Build both source and test targets
  --configuration, -c     Build with configuration (debug|release) [default: debug]
  --disable-prefetching   
  --disable-sandbox       Disable using the sandbox when executing subprocesses
  --enable-build-manifest-caching
                          Enable llbuild manifest caching [Experimental]
  --no-static-swift-stdlib
                          Do not link Swift stdlib statically
  --package-path          Change working directory before any other operation
  --product               Build the specified product
  --show-bin-path         Print the binary output path
  --static-swift-stdlib   Link Swift stdlib statically
  --target                Build the specified target
  --verbose, -v           Increase verbosity of informational output
  -Xcc                    Pass flag through to all C compiler invocations
  -Xcxx                   Pass flag through to all C++ compiler invocations
  -Xlinker                Pass flag through to all linker invocations
  -Xswiftc                Pass flag through to all Swift compiler invocations
  --help                  Display available options

可以看到build项目除了直接用build命令之外还可以加上一些额外的选项。这里做一下讲解

USAGE: swift build [options]

OPTIONS:
  --build-path            指定编译文件存放的路径(默认路径是./.build)
  --build-tests           编译源码和测试代码
  --configuration, -c     编译环境(debug|release),默认是debug
  --disable-prefetching   禁止prefetching
  --disable-sandbox       禁用沙盒
  --enable-build-manifest-caching 
                          打开llbuild清单缓存(实验功能,这个就是增量编译了)
  --no-static-swift-stdlib 不要静态link Swift stdlib
  --package-path          当自己指定源代码路径的时候使用此命令
  --product               编译指定的product
  --show-bin-path         打印二进制文件输出路径
  --static-swift-stdlib   静态link Swift stdlib
  --target                编译特定的target
  --verbose, -v           Increase verbosity of informational output
  -Xcc                    将标志传递给所有C编译器调用
  -Xcxx                   将标志传递给所有C++编译器调用
  -Xlinker                将标志传递给所有linker调用
  -Xswiftc                将标志传递给所有Swift编译器调用
  --help                  查看帮助

如果你只是一个swift开发人员,指定路径、指定环境是工作中会最常用到的命令。

  • 运行项目

执行命令

$ swift run --help
OVERVIEW: Build and run an executable product

USAGE: swift run [options] [executable [arguments ...]]

OPTIONS:
  --build-path            Specify build/cache directory [default: ./.build]
  --configuration, -c     Build with configuration (debug|release) [default: debug]
  --disable-prefetching   
  --disable-sandbox       Disable using the sandbox when executing subprocesses
  --enable-build-manifest-caching
                          Enable llbuild manifest caching [Experimental]
  --no-static-swift-stdlib
                          Do not link Swift stdlib statically
  --package-path          Change working directory before any other operation
  --skip-build            Skip building the executable product
  --static-swift-stdlib   Link Swift stdlib statically
  --verbose, -v           Increase verbosity of informational output
  -Xcc                    Pass flag through to all C compiler invocations
  -Xcxx                   Pass flag through to all C++ compiler invocations
  -Xlinker                Pass flag through to all linker invocations
  -Xswiftc                Pass flag through to all Swift compiler invocations
  --help                  Display available options

POSITIONAL ARGUMENTS:
  executable              The executable to run

和上面的build一样,在运行项目的时候,可以配置不同的环境参数来运行不同的target或环境

  • 添加依赖

通过SwiftPM创建项目的时候会在项目的根目录生成一个Package.swift的文件,这个文件就相当于cocoapods的Podfile 或者Carthage 的Cartfile.

首先来看看 HelloExcutable 这个项目的Package.swift文件:

// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "HelloExcutable",
    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.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "HelloExcutable",
            dependencies: []),
    ]
)

目前这个项目没有添加任何依赖,添加依赖的步骤如下:

1、引入一个依赖库SwiftNIO

import PackageDescription

let package = Package(
    name: "HelloExcutable",
    dependencies: [
        .package(url: "https://github.com/apple/swift-nio.git", from: "1.9.2")
    ],
    targets: [
        .target(
            name: "HelloExcutable",
            dependencies: ["NIO", "NIOHTTP1", "NIOFoundationCompat"]),
    ]
)

2、执行命令

swift build

这时候会下载依赖的package,下载完后会编译项目。

3、重新生成 xcodeproj文件(仅Mac适用)

swift package generate-xcodeproj

打开生成的 HelloExcutable.xcodeproj 文件就可以看到刚刚添加的依赖已经集成到项目里了:

添加dependencies之后的项目

Package.swift怎么写?

Package.swift是用于管理项目依赖以及项目结构的文件,文件内容就是一个 Package 类的实例。

Package

这里是Package这个Class的init方法:

final public class Package {

...

public init(name: String, //项目的名称
                pkgConfig: String? = default,
                providers: [PackageDescription.SystemPackageProvider]? = default,
                 products: [PackageDescription.Product] = default, // 对外公开的产物
             dependencies: [PackageDescription.Package.Dependency] = default, // 依赖
                  targets: [PackageDescription.Target] = default, // 项目的targets
    swiftLanguageVersions: [Int]? = default, // swift版本
        cLanguageStandard: PackageDescription.CLanguageStandard? = default, // c语言标准
      cxxLanguageStandard: PackageDescription.CXXLanguageStandard? = default) // c++语言标准
      
}

可以根据项目的需要,设置对应的参数。 比如要用SwiftPM做一个framework,那么需要设置products指定Framework的名字和target。

Package.Dependency

当项目中需要添加依赖的时候,需要设置dependencies参数, 是一个Package.Dependency类的集合,下面是Package.Dependency的部分源码:


extension Package.Dependency : Equatable {

    public static func package(url: String, from version: PackageDescription.Version) -> PackageDescription.Package.Dependency

    public static func package(url: String, _ requirement: PackageDescription.Package.Dependency.Requirement) -> PackageDescription.Package.Dependency

    public static func package(url: String, _ range: Range<PackageDescription.Version>) -> PackageDescription.Package.Dependency

    public static func package(url: String, _ range: ClosedRange<PackageDescription.Version>) -> PackageDescription.Package.Dependency
    
    ...
    
}

类似cocoapods 和 Carthage, Package.Dependency有两个参数,第一个参数设置依赖库的url,第二个参数设置依赖库的版本

设置版本的语法:

 .package(url:"", from: "1.0.0")                               (1.0.0 ..< 2.0.0)
 .package(url:"", from: "1.2.0")                               (1.2.0 ..< 2.0.0)
 .package(url:"", from: "1.5.8")                               (1.5.8 ..< 2.0.0)
 .package(url:"", .exactItem(Version(stringLiteral: "1.2.0"))  (==1.2.0)
 .package(url:"", .exactItem(Version(stringLiteral: "1.2.0"))  (==1.2.0)
 .package(url:"", .revisionItem("74663ec"))                    某次提交的revision的值
 .package(url:"", .branchItem("develop"))                      分支名
 .package(url:"", .localPackageItem)                           本地依赖
 .package(url:"", Version(stringLiteral: "1.2.3")...Version(stringLiteral: "1.2.8"))   (>=1.2.3 && <=1.2.8)
 .package(url:"", Version(stringLiteral: "1.2.3")..<Version(stringLiteral: "1.2.8"))   (>=1.2.3 && <1.2.8)

同样上面的Version也可以用以下方式来写:

Version(1, 2, 0) 相当于于 Version(stringLiteral: "1.2.0")
Version(1, 0, 0)..<Version(1, .max, .max) 意思是版本大于1.0.0 小于2.0.0

Target

另外一个比较重要的类是Target:

final public class Package {

...

public static func target(name: String, // target的名称
                  dependencies: [PackageDescription.Target.Dependency] = default, // target 的依赖,这里面主要指定Package添加的依赖的module的名字
                          path: String? = default, // target的路径,如果自定义文件夹需要设置此参数
                       exclude: [String] = default,  // target path中不希望被包含的path
                       sources: [String]? = default,  // 资源文件的路径
             publicHeadersPath: String? = default // 公共header文件的路径
             ) -> PackageDescription.Target

...

}

用于定义项目里的target。

本篇主要介绍了SwiftPM的一些基础用法,使用时需要注意以下几点:

1、dependencies里面的链接和版本一定要写对,target里的dependencies对应的总的dependencies中的module,一个依赖可以有多个module。

2、注意多个库依赖时的兼容性,如果出现卡着不动的时候,常常是依赖的版本有问题,可以逐步添加来排查问题。

3、Package.swift中的语法不要写错,注意 [ ]和 ""要成对存在,不要漏了前后的符号。

4、如果你不是在Mac上开发,可以使用Atom等支持高亮的编辑器来编辑Package.swift文件。

5、不要去尝试用它来管理iOS项目的依赖,现在还不支持,到支持的时候我会更新此文章。

希望本文能给你带来一些帮助,有疑问或者需要补充的地方欢迎留言。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生x阅读 15,967评论 3 119
  • 对于小笼包,江浙沪包邮区的人是最有感情的,而其中上海小笼讲究精巧,皮子薄,汤汁多,吃的是一个鲜字,夹的时候需用巧劲...
    天津吕老师阅读 660评论 0 1
  • 这世上有许多深深浅浅的缘分,人的一生何其短暂,茫茫人海,能遇到心灵相通的人几率并不多,能遇到灵魂伴侣的...
    赤色风铃66阅读 303评论 0 0
  • 寒风呼啸的冬天,正是吃上一碗清炖羊肉的好时节。虽说羊肉色鲜味美,营养价值极高,但它本身的膻味让很多吃货们望而却步。...
    大鑫_zou阅读 855评论 0 7