Swift Package Manager 清单 API 重新设计

Swift 4 中的 Package Manager 包含了重新设计的 Package.swift 清单 API。新的 API 更易于使用,并遵循了设计指南。Swift 3 Package Manager 中的目标推断规则是常见的困惑来源。我们修改了这些规则,并移除了大部分推断,倾向于在清单中显式指定 package 结构的实践。

Swift 3 的 package 将继续工作,因为 Swift 4 中的 Package Manager 是向后兼容的。清单版本由 package 的工具版本选择。工具版本在清单的第一行指定,使用特殊的注释语法:// swift-tools-version:<specifier>。省略此特殊注释的 package 将默认使用工具版本 3.1.0。

工具版本还决定了用于编译 package 源代码的默认 Swift 语言版本。现有的 Swift 3 package 将在 Swift 3 兼容模式下编译。如果您不想要默认版本,可以选择在 Swift 3 和 Swift 4 清单中使用 swiftLanguageVersions 属性来设置用于编译该 package 的语言版本。这意味着可以在不将源代码升级到 Swift 4 的情况下,升级 package 以使用较新的清单格式。

在 Swift 4 中创建新的 Package

使用 init 子命令在 Swift 4 中创建一个新的 package

$ mkdir mytool && cd mytool
$ swift package init
$ swift build
$ swift test

上述命令生成的 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: "mytool",
    products: [
        // Products define the executables and libraries produced by a package, and make them visible to other packages.
        .library(
            name: "mytool",
            targets: ["mytool"]),
    ],
    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 defines 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: "mytool",
            dependencies: []),
        .testTarget(
            name: "mytoolTests",
            dependencies: ["mytool"]),
    ]
)

上述 Swift 4 清单与之前的清单格式之间有三个主要区别

  1. 工具版本 4.0 使用 // swift-tools-version:4.0 行指定。
  2. 所有 target 及其依赖项都必须显式声明。
  3. 公共 target 使用新的 product API 作为 product 销售。Swift 4 package 中的 target 可以依赖于其他 package 的 product,也可以依赖于同一 package 的 target。

自定义 Target 布局

新的清单支持自定义 package 的布局。Package 不再需要遵循复杂的、基于约定的布局规则。只有一个规则:如果未提供 target 路径,将搜索目录 SourcesSourcesrcsrcsTests(按顺序)以查找 target。

自定义布局使将 C 库移植到 Swift Package Manager 变得更容易。以下是服务器端 Swift 社区中使用的两个 C 库的清单

LibYAML

Copyright (c) 2006-2016 Kirill Simonov, licensed under MIT license (https://github.com/yaml/libyaml/blob/master/LICENSE)
// swift-tools-version:4.0

import PackageDescription

let packages = Package(
    name: "LibYAML",
    products: [
        .library(
            name: "libyaml",
            targets: ["libyaml"]),
    ],
    targets: [
        .target(
            name: "libyaml",
            path: ".",
            sources: ["src"])
    ]
)

Node.js http-parser

Copyright by Authors (https://github.com/nodejs/http-parser/blob/master/AUTHORS), licensed under MIT license (https://github.com/nodejs/http-parser/blob/master/LICENSE-MIT)
// swift-tools-version:4.0

import PackageDescription

let packages = Package(
    name: "http-parser",
    products: [
        .library(
            name: "httpparser",
            targets: ["http-parser"]),
    ],
    targets: [
        .target(
            name: "http-parser",
            publicHeaders: ".",
            sources: ["http_parser.c"])
    ]
)

依赖项解析

由于 Swift 3 Package Manager 不理解 Swift 4 清单格式,它将自动忽略包含 Swift 4 清单的 Git 标签。因此,如果 package 升级到 Swift 4 清单,Swift 3 Package Manager 将选择包含 Swift 3 清单的最后一个标签。但是,Swift 4 中的 Package Manager 将选择最新的可用版本,而无需考虑清单版本。

将现有 Package 更新到 Swift 4 清单格式

按照以下步骤更新现有 package 以使用 Swift 4 清单格式。

$ cd mypackage
$ swift package tools-version --set-current
    ...
    dependencies: [
-    .Package(url: "https://github.com/apple/example-package-fisheryates.git", majorVersion: 2),
+    .package(url: "https://github.com/apple/example-package-fisheryates.git", from: "2.0.0"),

-    .Package(url: "https://github.com/apple/example-package-playingcard.git", majorVersion: 3, minor: 3),
+    .package(url: "https://github.com/apple/example-package-playingcard.git", .upToNextMinor(from: "3.3.0")),
    ]
    ...
    ...
    targets: [
        .target(
            name: "Foo"),
        .testTarget(
            name: "FooTests",
            dependencies: ["Foo"]),
    ]
    ...
    ...
    targets: [
        .target(
            name: "Foo",
            path: "."), // The sources are located in package root.
        .target(
            name: "Bar",
            path: "Sources") // The sources are located in directory Sources/.
    ]
    ...
    ...
    products: [
        .library(
            name: "Foo",
            targets: ["Foo", "Bar"]),
    ],
    ...
    ...
    swiftLanguageVersions: [3]
    ...