Go 如何基于 MVS 解决依赖关系问题

文摘   2024-06-28 22:22   中国香港  

摘要:文章主要介绍了 Go 如何解决依赖关系和版本冲突问题,以及模块管理系统,包括 go.mod 文件的作用、go get 和 go install 命令的使用

Go 使用一种名为 "最小版本选择(Minimal version selection)"(MVS[1])的方法来处理依赖关系和解决版本冲突。MVS 听起来很复杂,但要点如下:它为每个模块挑选最低版本,以满足其他模块的所有要求。这样,它就能在满足所有相关需求的同时,尽可能减少依赖性。

例如,假设我们有三个模块:A、B 和 C。

模块 A@1.3.2 和模块 B@1.0.0 都依赖于模块 C。

但 A 需要 C@2.3.2,B 需要 C@2.3.0。

同时 C 也有一个更新的版本,即 2.4.1。

Go 如何决定使用哪个版本的 C?

如果选择的 2.3.2 版本与模块 B 不兼容怎么办?

Go 模块使用语义版本控制(semver[2])来处理兼容性问题。如果模块遵循 semver,则任何破坏性修改都应修改主版本(major.minor.patch)。由于 2.3.0 和 2.3.2 两个版本的主要版本(2.3.x)相同,因此它们应该相互向后兼容。模块作者有责任确保不同版本保持兼容。

如果 A 需要 3.0.0 版本的 C,这会破坏与 B 的兼容性,怎么办?

当模块达到主版本 2 或更高版本时,其路径会包含主版本号(如 /v2 或 /v3):

github.com/user/project/v2
github.com/user/project/v3

不同主要版本的模块被视为不同的模块。因此,我们可以在同一项目中同时使用 C@2.3.2 和 C@3.0.0。

例如,我们的 go.mod 文件可能如下所示:

module yourproject

require (
    github.com/user/A v1.0.0
    github.com/user/B v1.0.0
)
require (
    github.com/user/C/v2 v2.3.2 // indirect
    github.com/user/C/v3 v3.0.0 // indirect
)

// indirect 注释表示这里的项目没有直接导入 C 模块,而是因为 A 或 B 需要它才导入的。

还有一个要注意点:go get 不会更新或添加缺失的测试依赖项。要包含这些依赖项,请使用 -t 标志,如 go get -t ./...

go get

这里举几个例子,看看 go get 在不同情况下是如何工作的。

使用 go get .或 go get ./...查找当前目录或其子目录中所有缺失的依赖项,并将其添加到 go.mod 文件中。这意味着它会检查任何尚未列出的依赖项,并将其添加进来。除非你特别要求,否则它不会将现有的依赖项更新到最新版本,比如下一个例子中的 -u 标志。

go get -u .

在 go get .中使用 -u 标志,会将当前目录中的现有依赖项更新到最新的次版本或补丁版本。请记住,它不会更新到新的主版本,因为它们被视为不同的模块。要将主模块的所有依赖项更新到最新版本,可以使用 go get -u ./...

在下面的几个情况下,即使不指定 -u 标志,go get 仍会更新过时或丢失的依赖项。

go get github.com/user/project

此命令下载模块 github.com/user/project,并将其添加到 go.mod 文件中。如果该模块已列在该文件中,则会将其更新为最新的次版本或补丁版本。基本上,如果你不指定版本(或版本查询后缀),它会假定你想升级到最新版本,就像使用 go get github.com/user/project@upgrade 一样。

go get github.com/user/project@v1.2.3

此命令将模块更新到指定版本,即 v1.2.3。根据当前的版本,它可能会升级或降级模块以匹配该版本。

go install: build and install packages

与下载依赖项以便在项目中使用其源代码不同,go install 会将依赖项的源代码编译成二进制文件,并将其移动到 $GOPATH/bin 目录中进行安装。这样就可以在终端上使用它了。

例如:

$ go install golang.org/x/tools/gopls@latest

运行该命令并查看 GOBIN 在您的 $PATH 中,您就可以在终端中运行 gopls。

如果只是在没有任何参数的情况下运行 go install 会怎样呢?go install 就会下载缺失的依赖项,并在当前目录下构建当前模块。这导致一些人误用 go install 来管理依赖关系,因为它确实会下载依赖关系。但这并不是它的主要工作,它实际上是要构建你的项目,并将生成的二进制文件安装到 $GOBIN 目录中。

go install 用于构建和安装软件包,而 go get 用于管理依赖关系。有些开发者经常会感到困惑,这是因为在旧版本的 Go 中,go get 确实是用来在更新 go.mod 文件后构建软件包,然后将它们安装到 $GOPATH/bin。

但从 Go 1.16 开始,go install 成为了构建和安装的首选命令,而 go get 则专注于管理 go.mod 文件中的需求。

参考资料
[1]

MVS: https://go.dev/ref/mod#minimal-version-selection

[2]

semver: https://semver.org/


Go Official Blog
Golang官方博客的资讯翻译及独家解读
 最新文章