Go Wiki: 解决修改模块路径引起的问题
意外的模块路径
用户在他们的项目 my-go-project
中进行 go get -u
时可能会遇到如下错误
$ cd my-go-project
$ go get -u ./...
[...]
go: github.com/golang/lint@v0.0.0-20190313153728-d0100b6bd8b3: parsing go.mod: unexpected module path "golang.org/x/lint"
[...]
Exit code 1
golang.org/x/lint
是一个模块,其 git 仓库和模块名称曾是 github.com/golang/lint
,后来迁移到 git 仓库 golang.org/x/lint
并将模块名称重命名为 golang.org/x/lint
。Go 工具目前在尝试理解新 git 仓库中的旧模块名称时会遇到问题:golang/go#30831。
这个问题出现在 my-go-project
中是因为 my-go-project
或其某个传递依赖在模块图中有一条指向旧的 github.com/golang/lint
模块名称的路径。
例如,如果 my-go-project
本身依赖于旧的 github.com/golang/lint
模块名称
$ GO111MODULE=on go mod graph
my-go-project github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7
或者,也许 my-go-project
依赖于旧版本的 google.golang.org/grpc
,而后者依赖于旧的 github.com/golang/lint
模块名称
$ GO111MODULE=on go mod graph
my-go-project google.golang.org/grpc@v1.16.0
google.golang.org/grpc@v1.16.0 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7
最后,也许 my-go-project
依赖于另一个依赖项,该依赖项需要旧版本的 google.golang.org/grpc
,而后者又依赖于旧的 github.com/golang/lint
模块名称
$ GO111MODULE=on go mod graph
my-go-project some/dep@v1.2.3
...
another/dep@v1.4.2 google.golang.org/grpc@v1.16.0
google.golang.org/grpc@v1.16.0 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7
移除对该名称的引用
在 Go 工具更新以理解更改了模块路径的模块之前(追踪进展:golang/go#30831),解决方案是更新模块图,使其不再包含指向旧模块名称的路径。
以上面的例子为例,我们将探讨如何更新模块图,使其不再包含指向 github.com/golang/lint
的路径。
修复第一个例子很简单,唯一的链接来自 my-go-project
- 这是用户可以控制的!在 go.mod
中用新的位置替换旧位置 - 将 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7
替换为 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f
- 从而从模块图中移除该链接
$ GO111MODULE=on go mod graph
my-go-project golang.org/x/lint@v0.0.0-20190301231843-5614ed5bae6f
修复第二个例子需要更多步骤,但本质上是相同的过程:google.golang.org/grpc@v1.16.0
提供了指向 github.com/golang/lint
的链接,因此 google.golang.org/grpc
应该更新其 go.mod
,将 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7
替换为 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f
(幸运的是,这已经在 v1.17.0
中完成)。然后,my-go-project
应该更新其 go.mod
以包含新版本的 google.golang.org/grpc
,这样我们就有了
$ GO111MODULE=on go mod graph
my-go-project google.golang.org/grpc@v1.17.0
google.golang.org/grpc@v1.17.0 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
修复第三个例子与第二个类似:更新到 another/dep
的新版本,该版本引入了不包含 github.com/golang/lint
引用的新版本 google.golang.org/grpc
。
太棒了!问题解决了 - Go 工具不再需要考虑指向 github.com/golang/lint
的路径,因此在执行 go get -u
时不会出现这个问题。
一个更难的问题:移除尾部历史
这些方法都很好,应该能解决大多数用户的问题。
然而,有一种情况会更加复杂:当模块依赖图中存在循环时。考虑这个模块依赖图
并且,让我们假设 some/lib
曾经依赖于 github.com/golang/lint
。
让我们看看包含版本信息的模块依赖图
$ go mod graph
my-go-lib some/lib@v1.7.0
some/lib@v1.7.0 some-other/lib@v2.5.3
some/lib@v1.7.0 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
some-other/lib@v2.5.3 some/lib@v1.6.0
some/lib@v1.6.0 some-other/lib@v2.5.0
some/lib@v1.6.0 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
some-other/lib@v2.5.0 some/lib@v1.3.1
some/lib@v1.3.1 some-other/lib@v2.4.8
some/lib@v1.3.1 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
some-other/lib@v2.4.8 some/lib@v1.3.0
some/lib@v1.3.0 some-other/lib@v2.4.7
some/lib@v1.3.0 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7
使用 golang.org/x/exp/cmd/modgraphviz 可视化
在这里我们看到,即使 some/lib
的最新几个版本正确地依赖于 golang.org/x/lint
,但 some/lib
和 some-other/lib
共享一个循环,这意味着很可能存在一条追溯到很早之前的历史路径。
出现这种路径的原因是,升级版本的操作通常是独立的原子操作:当 some/lib
升级其 some-other/lib
的版本并发布自身的新版本时,some-other/lib
的最新版本仍然依赖于 some/lib
的旧版本。也就是说,单独升级这两个库中的任何一个都不足以移除历史链条。
为了移除历史链条并彻底从模块图中移除旧的 github.com/golang/lint
引用,这两个库必须同时升级对彼此的依赖版本。
原子式地升级两个库的版本
移除 github.com/golang/lint
的解决方案是,首先确保 some/lib
不再依赖于 github.com/golang/lint
,然后将 some/lib
和 some-other/lib
都升级到彼此不存在的未来版本。我们想要这样的模块图
my-go-lib some/lib@v1.7.1
some/lib@v1.7.1 some-other/lib@v2.5.4
some/lib@v1.7.1 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
some-other/lib@v2.5.4 some/lib@v1.7.1
由于 some/lib
和 some-other/lib
在同一版本上相互依赖,因此在时间上没有路径可以追溯到提供 github.com/golang/lint
的某个点。
以下是实现原子版本升级的步骤,假设 some/lib
当前版本为 v1.7.0
,some-other/lib
当前版本为 v2.5.3
- 验证错误确实存在
- 在
some/lib
和some-other/lib
中运行GO111MODULE=on go get -u ./...
。 - 在两个仓库中,您应该会看到如下错误 go:
github.com/golang/lint@v0.0.0-20190313153728-d0100b6bd8b3: parsing go.mod: unexpected module path "golang.org/x/lint"
。
- 在
- 验证
some/lib
的最新版本依赖于golang.org/x/lint
而不是github.com/golang/lint
。如果移除了历史链条但保留了对github.com/golang/lint
的错误依赖,那就太可惜了! - 使用 alpha 标签将两个库的版本都升级到彼此不存在的未来版本(使用 alpha 标签更安全,因为 Go Modules 在评估模块的最新发布版本时不会将 alpha 版本视为更新版本)
some/lib
将其对some-other/lib
的依赖从v2.5.3
更改为v2.5.4-alpha
。some/lib
标记提交v1.7.1-alpha
并推送提交和标签。some-other/lib
将其对some/lib
的依赖从v1.6.0
更改为v1.7.1-alpha
。some-other/lib
标记提交v2.5.4-alpha
并推送提交和标签。
- 在 alpha 状态下验证结果
- 在
some/lib
中运行GO111MODULE=on go build ./...
&&go test ./...
。 - 在
some-other/lib
中运行GO111MODULE=on go build ./...
&&go test ./...
。 - 在两个仓库中运行
GO111MODULE=on go mod graph
并断言没有指向github.com/golang/lint
的路径。 - 注意:
go get -u
仍然无法工作,因为如上所述,在评估最新版本时不会考虑 alpha 版本。
- 在
- 如果一切看起来都正常,继续将彼此的版本再次升级到不存在的未来版本
some/lib
将其对some-other/lib
的依赖从v2.5.4-alpha
更改为v2.5.4
some/lib
标记提交v1.7.1
并推送提交和标签。some-other/lib
将其对some/lib
的依赖从v1.7.1-alpha
更改为v1.7.1
。some-other/lib
标记提交v2.5.4
并推送提交和标签。
- 验证错误不再存在
- 在
some/lib
和some-other/lib
中运行GO111MODULE=on go get -u ./...
。 - 不应再出现解析
go.mod: unexpected module path "golang.org/x/lint"
的错误。
- 在
- 目前,
some/lib
和some-other/lib
的go.sum
文件是不完整的。这是因为我们依赖了未来、不存在的模块版本,所以在整个过程完成之前无法生成 go.sum 条目。现在来修复这个问题- 在
some/lib
中运行GO111MODULE=on go mod tidy
。 - 提交,标记提交
v1.7.2
,并推送提交和标签。 - 在
some-other/lib
中运行GO111MODULE=on go mod tidy
。 - 提交,标记提交
v2.5.5
,并推送提交和标签。
- 在
- 最后,确保
my-go-project
依赖于这些没有长历史尾巴的some/lib
和some-other/lib
新版本- 将
my-go-project
的go.mod
中的some/lib v1.7.0
条目更改为some/lib 1.7.2
。 - 在
my-go-project
中运行GO111MODULE=on go get -u ./...
进行测试。
- 将
请注意,在步骤 5.b 和 5.d 之间,用户会遇到问题:some/lib
发布了一个版本,该版本依赖于 some-other/lib
的一个不存在的版本。因此,理想情况下,此过程应实时完成,以便步骤 5.d 在步骤 5.b 完成后尽快完成,从而将中断窗口最小化。
更大的循环
这个例子解释了当模块图中存在涉及两个包的循环时如何移除历史链条,但如果存在涉及更多包的循环呢?例如,考虑以下模块图
这些模块图都包含循环(后一个例子)或互连模块(前一个例子),涉及四个模块,而不是我们之前看到的简单两个模块的例子。然而,过程大体相同,只是在步骤 3 和 5 中,我们将所有四个模块都升级到彼此不存在的未来版本,类似地,在步骤 4 和 6 中,我们将测试所有四个模块,在步骤 7 中修复所有四个模块的 go.sum 文件。
更一般地说,上述过程适用于任何涉及 n 个模块的互连模块组:每个主要步骤都只需 n 个模块协同操作。
此内容是 Go Wiki 的一部分。