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 时不会出现这个问题。

一个更难的问题:移除尾部历史

这些方法都很好,应该能解决大多数用户的问题。

然而,有一种情况会更加复杂:当模块依赖图中存在循环时。考虑这个模块依赖图

Module Dependency Graph With A Cycle

并且,让我们假设 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 可视化

A Module Dependency Graph With Trailing History

在这里我们看到,即使 some/lib 的最新几个版本正确地依赖于 golang.org/x/lint,但 some/libsome-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/libsome-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

A Module Dependency Graph Without Trailing History

由于 some/libsome-other/lib 在同一版本上相互依赖,因此在时间上没有路径可以追溯到提供 github.com/golang/lint 的某个点。

以下是实现原子版本升级的步骤,假设 some/lib 当前版本为 v1.7.0some-other/lib 当前版本为 v2.5.3

  1. 验证错误确实存在
    1. some/libsome-other/lib 中运行 GO111MODULE=on go get -u ./...
    2. 在两个仓库中,您应该会看到如下错误 go: github.com/golang/lint@v0.0.0-20190313153728-d0100b6bd8b3: parsing go.mod: unexpected module path "golang.org/x/lint"
  2. 验证 some/lib 的最新版本依赖于 golang.org/x/lint 而不是 github.com/golang/lint。如果移除了历史链条但保留了对 github.com/golang/lint 的错误依赖,那就太可惜了!
  3. 使用 alpha 标签将两个库的版本都升级到彼此不存在的未来版本(使用 alpha 标签更安全,因为 Go Modules 在评估模块的最新发布版本时不会将 alpha 版本视为更新版本)
    1. some/lib 将其对 some-other/lib 的依赖从 v2.5.3 更改为 v2.5.4-alpha
    2. some/lib 标记提交 v1.7.1-alpha 并推送提交和标签。
    3. some-other/lib 将其对 some/lib 的依赖从 v1.6.0 更改为 v1.7.1-alpha
    4. some-other/lib 标记提交 v2.5.4-alpha 并推送提交和标签。
  4. 在 alpha 状态下验证结果
    1. some/lib 中运行 GO111MODULE=on go build ./... && go test ./...
    2. some-other/lib 中运行 GO111MODULE=on go build ./... && go test ./...
    3. 在两个仓库中运行 GO111MODULE=on go mod graph 并断言没有指向 github.com/golang/lint 的路径。
    4. 注意:go get -u 仍然无法工作,因为如上所述,在评估最新版本时不会考虑 alpha 版本。
  5. 如果一切看起来都正常,继续将彼此的版本再次升级到不存在的未来版本
    1. some/lib 将其对 some-other/lib 的依赖从 v2.5.4-alpha 更改为 v2.5.4
    2. some/lib 标记提交 v1.7.1 并推送提交和标签。
    3. some-other/lib 将其对 some/lib 的依赖从 v1.7.1-alpha 更改为 v1.7.1
    4. some-other/lib 标记提交 v2.5.4 并推送提交和标签。
  6. 验证错误不再存在
    1. some/libsome-other/lib 中运行 GO111MODULE=on go get -u ./...
    2. 不应再出现解析 go.mod: unexpected module path "golang.org/x/lint" 的错误。
  7. 目前,some/libsome-other/libgo.sum 文件是不完整的。这是因为我们依赖了未来、不存在的模块版本,所以在整个过程完成之前无法生成 go.sum 条目。现在来修复这个问题
    1. some/lib 中运行 GO111MODULE=on go mod tidy
    2. 提交,标记提交 v1.7.2,并推送提交和标签。
    3. some-other/lib 中运行 GO111MODULE=on go mod tidy
    4. 提交,标记提交 v2.5.5,并推送提交和标签。
  8. 最后,确保 my-go-project 依赖于这些没有长历史尾巴的 some/libsome-other/lib 新版本
    1. my-go-projectgo.mod 中的 some/lib v1.7.0 条目更改为 some/lib 1.7.2
    2. my-go-project 中运行 GO111MODULE=on go get -u ./... 进行测试。

请注意,在步骤 5.b 和 5.d 之间,用户会遇到问题:some/lib 发布了一个版本,该版本依赖于 some-other/lib 的一个不存在的版本。因此,理想情况下,此过程应实时完成,以便步骤 5.d 在步骤 5.b 完成后尽快完成,从而将中断窗口最小化。

更大的循环

这个例子解释了当模块图中存在涉及两个包的循环时如何移除历史链条,但如果存在涉及更多包的循环呢?例如,考虑以下模块图

Module Dependency Graph With Four Related Cycles

Module Dependency Graph With One Four Vertex Cycle

这些模块图都包含循环(后一个例子)或互连模块(前一个例子),涉及四个模块,而不是我们之前看到的简单两个模块的例子。然而,过程大体相同,只是在步骤 3 和 5 中,我们将所有四个模块都升级到彼此不存在的未来版本,类似地,在步骤 4 和 6 中,我们将测试所有四个模块,在步骤 7 中修复所有四个模块的 go.sum 文件。

更一般地说,上述过程适用于任何涉及 n 个模块的互连模块组:每个主要步骤都只需 n 个模块协同操作。


此内容是 Go Wiki 的一部分。