模块、包和版本

未匹配的标注

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

模块由 模块路径 标识,该路径在 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
发起讨论 只看当前版本


暂无话题~