Go Wiki:解决修改的模块路径引起的问题

意外的模块路径

在项目 my-go-project 上工作的用户在 go get -u 期间可能会遇到如下错误

$ cd my-go-project
$ go get -u ./...
[...]
go: github.com/golang/[email protected]: 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/[email protected]

或者,my-go-project 可能依赖于旧版本的 google.golang.org/grpc,而旧版本的 google.golang.org/grpc 依赖于旧的 github.com/golang/lint 模块名称

$ GO111MODULE=on go mod graph
my-go-project google.golang.org/[email protected]
google.golang.org/[email protected] github.com/golang/[email protected]

最后,my-go-project 可能依赖于另一个依赖项,而该依赖项需要旧版本的 google.golang.org/grpc,而旧版本的 google.golang.org/grpc 又依赖于旧的 github.com/golang/lint 模块名称

$ GO111MODULE=on go mod graph
my-go-project some/[email protected]
...
another/[email protected] google.golang.org/[email protected]
google.golang.org/[email protected] github.com/golang/[email protected]

删除对名称的引用

在 Go 工具更新为能够理解已更改其模块路径的模块(在 golang/go#30831 中跟踪)之前,解决此问题的办法是更新图形,以便不再有路径指向旧模块名称。

使用上述示例,我们将探讨如何更新图形,以便不再有路径指向 `github.com/golang/lint`。

修复第一个示例很简单,唯一的链接来自 `my-go-project` - 由用户控制!使用 `go.mod` 中的新位置替换旧位置 - 将 `github.com/golang/[email protected]` 替换为 `golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f` - 从图形中删除链接

$ GO111MODULE=on go mod graph
my-go-project golang.org/x/[email protected]

修复第二个示例涉及更多步骤,但本质上是相同的过程:`google.golang.org/[email protected]` 提供了指向 `github.com/golang/lint` 的链接,因此 `google.golang.org/grpc` 应将其 `go.mod` 从 `github.com/golang/[email protected]` 更新为 `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/[email protected]
google.golang.org/[email protected] golang.org/x/[email protected]

修复第三个示例与第二个示例类似:更新到 `another/dep` 的较新版本,其中引入了 `google.golang.org/grpc` 的较新版本,该版本不包含对 `github.com/golang/lint` 的引用。

万岁!问题已解决 - 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/[email protected]
some/[email protected] some-other/[email protected]
some/[email protected] golang.org/x/[email protected]
some-other/[email protected] some/[email protected]
some/[email protected] some-other/[email protected]
some/[email protected] golang.org/x/[email protected]
some-other/[email protected] some/[email protected]
some/[email protected] some-other/[email protected]
some/[email protected] golang.org/x/[email protected]
some-other/[email protected] some/[email protected]
some/[email protected] some-other/[email protected]
some/[email protected] github.com/golang/[email protected]

使用 golang.org/x/exp/cmd/modgraphviz 可视化

A Module Dependency Graph With Trailing History

在这里,我们看到即使 `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/libsome-other/lib 都提升到彼此不存在的未来版本。我们需要这种类型的图表

my-go-lib some/[email protected]
some/[email protected] some-other/[email protected]
some/[email protected] golang.org/x/[email protected]
some-other/[email protected] some/[email protected]

A Module Dependency Graph Without Trailing History

由于 some/libsome-other/lib 在同一版本中相互依赖,因此没有时间倒退的路径,无法提供 github.com/golang/lint

以下是实现此原子版本提升的步骤,假设 some/lib 位于 v1.7.0,而 some-other/lib 位于 v2.5.3

  1. 验证错误确实存在
    1. some/libsome-other/lib 中运行 GO111MODULE=on go get -u ./...
    2. 在两个存储库中,你都应该看到错误 go:github.com/golang/[email protected]: 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 标记将两个库提升到彼此不存在的未来版本(这更安全,因为在评估模块的最新发布版本时,go 模块不会将 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. GO111MODULE=on go mod tidysome/lib 中。
    2. 提交,标记提交 v1.7.2,并推送提交和标记。
    3. GO111MODULE=on go mod tidysome-other/lib 中。
    4. 提交,标记提交 v2.5.5,并推送提交和标记。
  8. 最后,让我们确保 my-go-project 依赖于这些新版本的 some/libsome-other/lib,这些版本没有很长的历史记录
    1. my-go-project go.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.b 之后尽快完成步骤 5.d,从而尽可能缩小中断窗口。

较大的循环

此示例解释了在图中涉及两个包的循环存在时删除历史记录的过程,但是如果涉及更多包的循环会怎样?例如,考虑以下图表

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 的一部分。