模块、包和版本

未匹配的标注

模块由一系列已发布、版本化、被分发的包构成。可以直接从版本控制仓库或模块代理服务器下载模块。

模块由 模块路径 标识,该路径在 go.mod 文件 中声明,并与模块依赖信息放在一起。模块根目录是指包含 go.mod 文件的目录。主模块是指包含了 go 命令的目录对应的模块。

模块下的每个包都是一系列同目录下、将被编译到一起的文件集合。包路径是模块路径和包含包的子目录(相对于模块根目录的路径)拼起来的结果。比如,模块 "golang.org/x/net" 包含了目录 html 下的包。则这个包路径就是 "golang.org/x/net/html"

模块路径#

模块路径就是模块的规范名称,被 go.mod file 中的 module 指令 所声明。

模块路径应该描述模块做什么以及在哪里找到它。通常,模块路径由存储库根路径、存储库中的目录(通常为空)和主要版本后缀(仅适用于主要版本为 2 或更高)组成。

  • 存储库根路径是模块路径的一部分,对应开发模块的版本控制仓库的根目录。大多数模块都被定义在存储库根目录,因此这通常就是整个模块路径了。比如,golang.org/x/net 就是同名模块的存储库根路径。如欲了解更多 go 命令如何通过模块路径并使用 HTTP 请求定位仓库的信息,请参阅 寻找模块存储库
  • 如果模块并没有定义于仓库根目录,则模块子目录是命名目录的模块路径的一部分,且不包括主版本后缀。这一规则也作用于语义化版本标签的前缀。比如,golang.org/x/tools/gopls 表示模块在存储库根路径 golang.org/x/tools 的子目录 gopls 下,因此它具有模块子目录 gopls。参见 版本映射至提交 以及 同一仓库下的模块名称
  • 假设模块发布在版本 2 或更高,模块路径必须有像 /v2 这样的 主版本后缀。例如,路径为 golang.org/x/repo/sub/v2 的模块可以位于存储库 golang.org/x/repo/sub/sub/v2 子目录中。

如果某个模块需要被其他模块所依赖,则必须遵循这些规则,以便 go 命令可以查找和下载该模块。模块路径中允许的字符也有一些 语法限制

版本#

一个版本标示着模块的不可变快照,可以是 正式发行版本pre-release 版本。每个版本都以字母 v 开头,跟着语义版本。有关语义版本的格式化、解释和比较的详细信息,请参见 语义版本 2.0.0

总而言之,一个语义版本由三个非负整数(主要、次要和补丁版本,从左到右)用点分隔组成。补丁版本后面可以跟一个以连字符开头的标识符(例如 -pre-beta)。预发发行版或补丁版本后面可以跟以加号开头的构建元数据(build metadata)字符串。例如, v0.0.0v1.12.134v8.0.5-prev2.0.9+meta 都是有效版本。

版本的每个部分都表示该版本是否稳定,是否与之前的版本兼容。

  • 主要版本号 在发布不兼容的公共接口更改后,例如模块里的某个包被删除,必须递增,必须将次要和补丁版本设置为零。
  • 次要版本号 在发布向后兼容的更改后,例如添加新函数后,必须递增且补丁版本设置为零。
  • 补丁版本号 在不修改到公共接口的情况下,例如 Bug 修复或者做了一些优化,必须递增。
  • -pre 后缀意味着某个版本的预发版,在指定版本的后面添加。例如在 v1.2.3 之前,发布 v1.2.3-pre 预发版。
  • 构建元数据会在版本比较时被忽略。附带构建元数据的标识符会在版本库中被忽略,然而在 go.mod 文件中会被保留。后缀 +incompatible 表示在迁移到模块版本主版本 2 或更高版本之前发布的版本。(详见 兼容非模块的代码库 )。

如果一个版本的主要版本是 0 或者它有一个预发布后缀,那么它就被认为是不稳定的。 不稳定的版本不受兼容性要求的限制。 例如,v0.2.0 可能与 v0.1.0 不兼容,v1.5.0-beta 可能与 v1.5.0 不兼容。

Go 可能会使用不遵循这些约定的标签、分支或修订来访问版本控制系统中的模块。 但是,在主模块中,go 命令会自动将不符合此标准的修订名称转换为规范版本。 作为此过程的一部分,go 命令还将删除构建元数据后缀(+incompatible 除外)。 这可能会导致一个 pseudo-version,一个编码修订标识符(例如 Git 提交哈希)的预发布版本和一个 来自版本控制系统的时间戳。 例如,命令 go get -d golang.org/x/net@daa7c041 会将提交哈希 daa7c041 转换为伪版本 v0.0.0-20191109021931-daa7c04131f5。 主模块之外需要规范版本,如果 go.mod 文件中出现像 master 这样的非规范版本,go 命令会报错。

伪版本#

伪版本是一种特殊格式的 预发布 版本 对版本控制存储库中特定修订的信息进行编码。 例如,v0.0.0-20191109021931-daa7c04131f5 是一个伪版本。

伪版本可能是指没有 语义版本标签 可用的修订。 它们可用于在创建版本标签之前测试提交,例如在开发分支上。

每个伪版本有三个部分:

  • 基础版本前缀 (vX.0.0 or vX.Y.Z-0), 它派生自修订之前的语义版本标记,如果没有这样的标记,则派生为 vX.0.0.
  • 时间戳 (yyyymmddhhmmss), 即创建修订的 UTC 时间。在 Git 中,这是提交时间,而不是作者时间.
  • 修订版标识符 (abcdefabcdef), 它是提交散列的 12 个字符的前缀,或者在 Subversion 中,用零填充的修订号.

每个伪版本可以是三种形式中的一种,具体取决于基础版本。这些形式确保伪版本比其基础版本高,但低于下一个标记版本.

  • vX.0.0-yyyymmddhhmmss-abcdefabcdef 当没有已知的基础版本时使用。与所有版本一样,主要版本 X 必须匹配模块的 主要版本后缀.
  • vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef 当基础版本是 vX.Y.Z-pre 等预发行版本时使用.
  • vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef 当基础版本是一个像 vX.Y.Z 这样的发布版本时使用。例如,如果基本版本是 v1.2.3, 则伪版本可能是 v1.2.4-0.20191109021931-daa7c04131f5.

多个伪版本可以通过使用不同的基础版本来指代相同的提交。当写入伪版本后,当较低版本被标记时,这会自然发生.

这些表单为伪版本提供了两个有用的属性:

  • 具有已知基本版本的伪版本排序高于那些版本,但低于后续版本的其他预发布版本.
  • 具有相同基本版本前缀的伪版本按时间顺序排序.

go 命令会执行若干个限制检查,以此确保模块作者能控制伪版本与其他版本进行区分,且伪版本引用的修订实际上是模块提交历史记录的一部分。

  • 如果指定了基础版本,那就必须有一个对应的语义版本标签,作为伪版本描述修订的起源。这可以防止开发人员绕过最小版本选择的限制,而使用比所有版本标签更高的伪版本进行对比。
  • 时间戳必须与修订的时间戳匹配。这可以防止攻击者滥用模块代理,从而使用无限个相同的伪版本。这个限制同时可以防范模块使用者更改版本的相对顺序。
  • 修订必须源自模块的仓库分支或者标签。这可以防止攻击者使用未经批准的更改或拉取请求。

伪版本不需要手动输入。有许多命令接受提交的哈希记录或者是分支名,将其自动转换为伪版本(如果可用,可以是标签版本)。比如下面的命令:

go get -d example.com/mod@master
go list -m -json example.com/mod@abcd1234

主版本后缀#

从第 2 个版本开始,模块路径必须有个类似 /v2 的后缀,以此来匹配主版本。例如:如果一个模块在 1.0.0 版本的路径为 example.com/mod,那么这个模块需要在 v2.0.0 版本时的路径为 example.com/mod/v2

主版本后缀实现导入兼容规则

如果旧包和新包具有相同的路径,那么这个新包需要对旧包向后兼容。

根据定义,模块在新主版本的包与旧主版本中的包不向后兼容。因此,从 v2 版本开始,包必须使用新的导入路径。这是通过模块路径添加一个主版本后缀实现的。由于模块路径是模块内每个包的导入路径的前缀,因此向模块路径添加主版本后缀可为每个不兼容版本,提供不同的导入路径。

主版本后缀不允许在 v0v1 出现。因为 v0 版本不稳定且没有兼容性保证,因此无需更改 v0v1 的模块路径。此外,对于大多数模块,v1 版本与最后一个 v0 版本向后兼容;与 v0 相比, v1 版本视为兼容性的承诺,而不是一个解除非兼容性的标志。

作为特殊情况,即使在 v0v1,以 gopkg.in/ 开头的模块路径必须始终具有主版本后缀。后缀必须以点开头,而不是斜杠(例如,gopkg.in/yaml.v2)。

主版本后缀允许模块的多个主版本共存于同一版本。由于钻石依赖性问题的存在,这一特性是必要的。通常情况下,如果一个模块需要两个不同版本的可传递依赖项,那么应当使用模块的更高版本。但是,如果这两个版本不兼容,那么这两个版本都不能满足所有客户端的需求。由于不兼容版本号之间必须具有不同的主版本数字,所以它们必须由主版本后缀决定不同的模块路径。这样解决了冲突:具有不同后缀的模块视为一个独立的模块,它们的包 – 甚至是相对于其模块根目录,在同一子目录下的包 – 其模块路径定义也将是不同的。

在迁移到模块之前,许多 Go 项目以 v2 或更高版本号来发布版本,而非使用主要的版本后缀 (可能在引入模块之前就没使用)。这些版本带有 +incompatible 标签注释(例如 v2.0.0+incompatible)。有关详细信息,请参阅非模块存储库的兼容性

将包解析为模块#

go 命令使用包路径加载包时,它需要确定哪个模块提供包。

go 命令从搜索构建列表开始,把具有包路径前缀的模块。例如,导入了包 example.com/a/b,并且模块 example.com/a 在构建列表中,那么 go 命令将检查 example.com/a 目录中是否包含需要导入的包。在 b 目录中必须至少存在一个扩展名为 .go 的文件,才能将其视为包。构建约束不适用于此目的。如果构建列表中只有一个模块提供包,则使用该模块。如果没有模块提供包,或者有多个模块提供包,则 go 命令会报错。-mod=mod 标志表示 go 命令尝试查找缺失的包模块,并更新 go.modgo.sumgo get 命令和 go mod tidy命令会自动触发此操作。

go 命令查找包路径的新模块时,它会检查 GOPROXY 环境变量,这是一个以逗号分隔的代理 URL,或者用关键字 directoff 表示的环境变量。代理 URL 表示的是,go 命令使用模块代理声明到的 GOPROXY 协议direct 关键字表示的是,go 命令必须与版本控制系统通信off 关键字表示的是,不应尝试任何通信。GOPRIVATEGONOPROXY 环境变量同样可用于控制 go 命令做这些的行为。

对于 GOPROXY 列表中的每个条目,go 命令会请求每个模块的最新版本可能提供的包(也就是每个包前缀)。请求成功的每个模块路径,go 命令会下载它的最新版本,并且会检查模块是否含有请求的包。如果一个或多个模块含有请求的包,会使用拥有最长路径的模块下的包。如果请求到一个或多个模块后发现,没有一个模块含有请求的包,会报告错误。如果模块没有被请求到,go 命令会尝试下一个 GOPROXY 列表的条目。如果没有剩余条目,则报告错误。

例如,假设 go 命令在寻找提供包的模块名为 golang.org/x/net/html,并且 GOPROXY 设置成了 https://corp.example.com,https://proxy.golang.orggo 命令可能会做如下的请求:

  • 请求到 https://corp.example.com/ (并行进行):
    • 请求最新版本的 golang.org/x/net/html
    • 请求最新版本的 golang.org/x/net
    • 请求最新版本的 golang.org/x
    • 请求最新版本的 golang.org
  • 如果所有对 https://corp.example.com/ 的请求有 404 或 410,请求到 https://proxy.golang.org/
    • 请求最新版本的 golang.org/x/net/html
    • 请求最新版本的 golang.org/x/net
    • 请求最新版本的 golang.org/x
    • 请求最新版本的 golang.org

找到合适的模块后,go 命令将添加一个新的 需求 与新模块的路径和版本到主模块的 go.mod 文件。这确保了将来加载相同的包时,将在相同的版本中使用相同的模块。如果解析后的包不是由主模块中的包导入的,则新需求会有一个新的 // indirect 注释.

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/go-mod/1.17/mod...

译文地址:https://learnku.com/docs/go-mod/1.17/mod...

上一篇 下一篇
贡献者:12
讨论数量: 0
发起讨论 只看当前版本


暂无话题~