3.2 使用包注册表

原文链接: https://docs.swift.org/latest/documentation/packagemanagerdocs/usingswiftpackageregistry/

3.2 使用包注册表

为 Swift Package Manager 配置并使用包注册表。

概述

Swift Package Manager 支持从任何实现了 SE-0292 以及相应服务规范的包注册表下载依赖。

在注册表中,包通过形如 scope.package-name 的包标识符来标识。

配置注册表

在 Swift Package Manager 中可以在两个层级配置注册表:

  • 项目级:注册表将用于该项目内的包。设置保存在 .swiftpm/configuration/registries.json。
  • 用户级:注册表将用于该用户的所有项目。设置保存在 ~/.swiftpm/configuration/registries.json。

你可以用 swift package-registry set 子命令来指定注册表 URL:

1
$ swift package-registry set https://packages.example.com 

上面这条命令在项目级把注册表设置为 https://packages.example.com。传入 --global 选项可以把注册表设置在用户级:

1
$ swift package-registry set --global https://packages.example.com 

最终生成的 registries.json 大致如下:

1
2
3
4
5
6
7
8
{
  "registries" : {
    "[default]" : {
      "url": "https://packages.example.com"
    }   
  },
  "version" : 1
}

JSON 键 [default] 表示 https://packages.example.com 这个注册表是「无边界的」,当某个作用域没有找到对应的注册表关联时就会使用它。

在这个例子中,https://packages.example.com 会应用到所有作用域。

添加注册表包依赖

注册表包依赖通过包标识符在 Package.swift 中声明。例如:

1
2
3
dependencies: [
    .package(id: "mona.LinkedList", .upToNextMajor(from: "1.0.0")),
],

包管理器会查询该包作用域所映射的注册表,以解析并下载合适的发布版本。

注册表认证

如果注册表需要认证,可以用 SE-0378 引入的 swift package-registry login 子命令来配置。

目前支持基本认证和令牌认证。

你可以通过设置相应选项(即用户名/密码之一,或者访问令牌)来提供凭据,也可以在提示时输入:

1
$ swift package-registry login https://packages.example.com

包管理器会把凭据保存到操作系统的凭据存储(例如 macOS 的钥匙串)或 netrc 文件(默认位于 ~/.netrc),并在发起注册表 API 请求时自动应用它们。

使用注册表解析依赖

解析注册表依赖包括以下步骤:

  1. 调用列出包的发布版本 API,获取某个包可用的各个版本。
  2. 通过获取某个包发布的清单文件计算依赖图。
  3. 确定要使用的包版本。

关于解析依赖的更多信息,参见解析与更新依赖。

为源代码管理依赖使用注册表

下面是一个源代码管理依赖的例子:

1
2
3
dependencies: [
    .package(url: "https://github.com/mona/LinkedList", .upToNextMajor(from: "1.0.0")),
],

注册表也可以用于源代码管理依赖。当依赖图是「混合」的(即同时包含源代码管理依赖和注册表依赖)时,这一点尤其有用。包管理器把来源不同的包视为不同的包,因此如果某个包同时以注册表依赖(例如 mona.LinkedList)和源代码管理依赖(例如 https://github.com/mona/LinkedList)的形式被引用,即使它们其实是同一个包,也会被视为不同的包,从而产生符号冲突。

Swift Package Manager 可以通过在源代码管理 URL 上做查找(例如 https://github.com/mona/LinkedList),看它是否与某个包标识符(例如 mona.LinkedList)相关联,从而对包去重。

你可以通过设置下列标志之一,来控制包管理器是否以及如何把注册表与源代码管理依赖结合使用:

  • --disable-scm-to-registry-transformation(默认):Swift Package Manager 不会把源代码管理依赖转换为注册表依赖。源代码管理依赖会从其对应 URL 下载;注册表依赖则会使用已配置的注册表(如果有)来解析和下载。
  • --use-registry-identity-for-scm:Swift Package Manager 会在注册表中查找源代码管理依赖,并尽可能使用它们的注册表标识,以帮助在两种来源之间对包去重。换句话说,假设 mona.LinkedList 是 https://github.com/mona/LinkedList 的包标识符,那么包管理器会把依赖图中的这两个引用视为同一个包。
  • --replace-scm-with-registry:Swift Package Manager 会在注册表中查找源代码管理依赖,并尽可能改用注册表来获取它们,而不是用源代码管理。换句话说,包管理器会先尝试从注册表下载源代码管理依赖,如果注册表中找不到该依赖,再回退到克隆源仓库。

从注册表下载依赖

注册表依赖解析完成之后,Swift Package Manager 可以从注册表下载所确定的包版本的源代码归档。

包的安全性

作为一项安全特性,Swift Package Manager 会对下载到的源代码归档执行校验和 TOFU(首次使用即信任)。关于包管理器如何使用首次使用即信任,参见包的安全性。

验证已签名的包

SE-0391 为 Swift Package Manager 增加了包签名支持。包管理器通过检查 HTTP 响应中是否存在 X-Swift-Package-Signature-Format 和 X-Swift-Package-Signature 头来判断下载到的归档是否已签名。

随后 Swift Package Manager 会根据用户的安全配置执行一系列校验。

关于包管理器注册表安全特性的更多信息,参见来自注册表的已签名包。

发布到注册表

swift package-registry publish 是一个把包发布发布到注册表的一体化命令。

包签名

注册表可以选择要求签名。关于已签名的注册表包的更多细节,参见来自注册表的已签名包。

签名格式
签名格式规范
cms-1.0.0SE-391

由于目前只支持一种签名格式,Swift Package Manager 生成的所有签名都采用 cms-1.0.0。

被签名的内容
源代码归档

签名是分离的,会作为 HTTP 请求的一部分发送给发布 API。它也会作为 HTTP 头包含在源代码归档的下载响应中,并且是包发布元数据的一部分。

包发布元数据

签名是分离的,会作为 HTTP 请求的一部分发送给发布 API。当前的 API 规范没有提供以原始形式获取这份元数据的端点。

更多细节请参考注册表规范。

包清单文件

Package.swift 和带版本的清单文件会各自单独签名。签名嵌入在对应的清单文件中。源代码归档是在清单文件签名之后才生成并签名的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// swift-tools-version: 5.7

import PackageDescription
let package = Package(
    name: "library",
    products: [ .library(name: "library", targets: ["library"]) ],
    targets: [ .target(name: "library") ]
)

// signature: cms-1.0.0;l1TdTeIuGdNsO1FQ0ptD64F5nSSOsQ5WzhM6/7KsHRuLHfTsggnyIWr0DxMcBj5F40zfplwntXAgS0ynlqvlFw==

当从注册表获取清单文件时,Swift Package Manager 会通过获取包发布元数据来检查其所在的源代码归档是否已签名。如果源代码归档已签名而清单文件未签名,就会失败。包管理器会从清单文件中提取并解析签名,然后按照与源代码归档签名类似的方式验证它。

包管理器会执行发布者 TOFU,以确保它对某个包始终保持一致。这意味着清单文件和源代码归档的签名者必须是同一个。

为了减少日志输出、从而减少噪音,与清单文件签名验证相关的诊断信息被设置为 DEBUG 级别。只有当用户对未签名的包、或者用不受信任证书签名的包选择了 prompt 选项时,包管理器的行为才会和源代码归档验证一样。

Swift Package Manager 注册表配置

注册表到作用域的映射

在解析或下载注册表包时,包管理器会查看项目级和用户级配置中的注册表到作用域的映射,以确定某个包的作用域被分配给了哪个注册表。

例如,给定下面这些配置文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 用户级配置(~/.swiftpm/configuration/registries.json)
{
  "registries": {
    "[default]": {
      "url": "https://global.example.com"
    },
    "foo": {
      "url": "https://global.example.com"
    },
  },
  "version": 1
}

// 本地配置(.swiftpm/configuration/registries.json)
{
  "registries": {
    "foo": {
      "url": "https://local.example.com"
    }
  },
  "version": 1
}
  • 对于包 foo.LinkedList,使用的是 https://local.example.com 上的注册表。(本地配置的优先级高于用户级配置。)
  • 对于包 bar.LinkedList,使用的是 https://global.example.com 上的注册表。(没有找到作用域 bar 的映射,因此使用 [default]。)

安全配置

注册表的安全配置在用户级 registries.json(~/.swiftpm/configuration/registries.json)中指定:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
{
  "security": {
    "default": {
      "signing": {
        "onUnsigned": "prompt", // 可选值之一:"error"、"prompt"、"warn"、"silentAllow"
        "onUntrustedCertificate": "prompt", // 可选值之一:"error"、"prompt"、"warn"、"silentAllow"
        "trustedRootCertificatesPath": "~/.swiftpm/security/trusted-root-certs/",
        "includeDefaultTrustedRootCertificates": true,
        "validationChecks": {
          "certificateExpiration": "disabled", // 可选值之一:"enabled"、"disabled"
          "certificateRevocation": "disabled"  // 可选值之一:"strict"、"allowSoftFail"、"disabled"
        }
      }
    },
    "registryOverrides": {
      // 这个例子展示了所有可以在注册表级别覆盖的配置
      "packages.example.com": {
        "signing": {
          "onUnsigned": "warn",
          "onUntrustedCertificate": "warn",
          "trustedRootCertificatesPath": <STRING>,
          "includeDefaultTrustedRootCertificates": <BOOL>,
          "validationChecks": {
            "certificateExpiration": "enabled",
            "certificateRevocation": "allowSoftFail"
          }
        }
      }
    },
    "scopeOverrides": {
      // 这个例子展示了所有可以在作用域级别覆盖的配置
      "mona": {
        "signing": {
          "trustedRootCertificatesPath": <STRING>,
          "includeDefaultTrustedRootCertificates": <BOOL>
        }
      }
    },
    "packageOverrides": {
      // 这个例子展示了所有可以在包级别覆盖的配置
      "mona.LinkedList": {
        "signing": {
          "trustedRootCertificatesPath": <STRING>,
          "includeDefaultTrustedRootCertificates": <BOOL>
        }
      }
    }
  },
  ...
}

配置有多级覆盖。某个包的配置是用下列取值(按优先级从高到低)计算出来的:

  1. packageOverrides(如果有)
  2. scopeOverrides(如果有)
  3. registryOverrides(如果有)
  4. default

上面例子中的 default JSON 对象包含了所有可配置的安全选项,以及在没有覆盖时所取的默认值。

  • signing.onUnsigned:表示包管理器将如何处理未签名的包。

    选项描述
    error包管理器会拒绝该包并使构建失败。
    prompt包管理器会提示用户,询问是否允许这个未签名的包。
    • 如果用户回答否,包管理器会拒绝该包并使构建失败。
    • 如果用户回答是,并且该包此前从未被下载过,它的校验和会被保存下来,用于校验和 TOFU。否则,如果该包此前下载过,它的校验和必须与先前的值一致,否则包管理器会拒绝该包并使构建失败。
    包管理器会记录用户的回答,以免反复提示。
    warn包管理器不会提示用户,而是在继续之前发出一个警告。
    silentAllow包管理器会允许这个未签名的包,既不提示用户也不发出警告。
  • signing.onUntrustedCertificate:表示包管理器将如何处理用不受信任的证书签名的包。

    选项描述
    error包管理器会拒绝该包并使构建失败。
    prompt包管理器会提示用户,询问是否允许这个用不受信任证书签名的包。
    • 如果用户回答否,包管理器会拒绝该包并使构建失败。
    • 如果用户回答是,包管理器会像处理未签名的包那样继续处理该包。
    包管理器会记录用户的回答,以免反复提示。
    warn包管理器不会提示用户,而是在继续之前发出一个警告。
    silentAllow包管理器会允许这个用不受信任证书签名的包,既不提示用户也不发出警告。
  • signing.trustedRootCertificatesPath:包含自定义受信任根证书的目录的绝对路径。包管理器会把这些根证书纳入它的信任存储,用于包签名的证书必须能链接到该存储中的根证书。这项配置允许在包、作用域和注册表级别覆盖。

  • signing.includeDefaultTrustedRootCertificates:表示包管理器是否应该把默认的受信任根证书纳入它的信任存储。这项配置允许在包、作用域和注册表级别覆盖。

  • signing.validationChecks:针对包签名的校验设置。

    校验描述
    certificateExpiration
    • enabled:包管理器会检查下载时的当前时间是否落在签名证书的有效期内。如果不在,包管理器会拒绝该包并使构建失败。
    • disabled:包管理器不执行这项检查。
    certificateRevocation除 disabled 之外,包管理器都会检查签名证书的吊销状态。目前包管理器只支持通过 OCSP 进行吊销检查。
    • strict:吊销检查必须成功完成,并且证书必须处于正常状态。如果吊销状态为已吊销或未知(包括不支持或检查失败),包管理器会拒绝该包并使构建失败。
    • allowSoftFail:仅当证书已被吊销时,包管理器才拒绝该包并使构建失败。包管理器允许证书的吊销状态为未知(包括不支持或检查失败)。
    • disabled:包管理器不执行这项检查。