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 仓库 golang.org/x/lint
并将其模块名称重命名为 golang.org/x/lint
之前,它的 git 仓库和模块名称是 github.com/golang/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
的新版本,该新版本带来了新版本的 google.golang.org/grpc
,而该新版本不包含对 github.com/golang/lint
的引用。
太棒了!问题解决了——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 ./...
。 - 在两个仓库中都应该会看到错误:
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 标签将两个库都更新到彼此不存在的未来版本(这更安全,因为 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 ./...
。 - 不应出现
parsing go.mod: unexpected module path "golang.org/x/lint"
错误。
- 在
- 目前,
some/lib
和some-other/lib
的go.sum
s 不完整。这是因为我们依赖于未来不存在的模块版本,所以直到过程完成才能生成 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 的一部分。