如何调试 Go mod 的各种异常
Go mod 自从诞生之日就带来了太多太多的争议,当然不能否认它的设计初衷是好的。然而在调试其各种异常时,却浪费了太多开发者的时间。可以毫不客气的说,从来没有一种语言的版本管理,能让人如此崩溃。
本文记录了一些我的踩坑经验,希望能给还在挣扎中的 Gopher 一些帮助。
go get
先来看看最近我遇到的一个问题:
$ go mod tidygo: foo.bar.com/foo/bar@v0.1.0: git remote add origin -- https://foo.bar.com/foo/bar.git in /root/goforge/pkg/mod/cache/vcs/e8ae9003fdd7f44246f94c535282e58436b3568a39734a76af8fac7b716a59b2: exit status 128:fatal: remote origin already exists.
光从字面上来看,像是一个 git 的问题。一顿 go mod why 和 go mod graph 操作后,也没有得到什么有价值的信息。
以我的个人摸索经验来看,
why和graph的输出就是一坨垃圾,只会给开发者带来更多的心智负担。
实际上,调试 go mod 问题最好的工具是 go get ,这样可以只会输出异常模块的依赖树,去掉那些烦人的干扰信息。如果再加上 -x 选项后,更是屡试不爽。继续拿我遇到的这个问题开刀:
$ go get-x foo.bar.com/foo/bar# get https://foo.bar.com/foo/bar?go-get=1# get https://foo.bar.com/foo/bar?go-get=1: 200 OK (0.508s)mkdir -p /root/goforge/pkg/mod/cache/vcs # git3 https://foo.bar.com/foo/bar.git# lock /root/goforge/pkg/mod/cache/vcs/e8ae9003fdd7f44246f94c535282e58436b3568a39734a76af8fac7b716a59b2.lockmkdir -p /root/goforge/pkg/mod/cache/vcs/e8ae9003fdd7f44246f94c535282e58436b3568a39734a76af8fac7b716a59b2 # git3 https://foo.bar.com/foo/bar.gitcd /root/goforge/pkg/mod/cache/vcs/e8ae9003fdd7f44246f94c535282e58436b3568a39734a76af8fac7b716a59b2; git init --bare0.011s# cd /root/goforge/pkg/mod/cache/vcs/e8ae9003fdd7f44246f94c535282e58436b3568a39734a76af8fac7b716a59b2; git init --barecd /root/goforge/pkg/mod/cache/vcs/e8ae9003fdd7f44246f94c535282e58436b3568a39734a76af8fac7b716a59b2; git remote add origin -- https://foo.bar.com/foo/bar.git0.006s# cd /root/goforge/pkg/mod/cache/vcs/e8ae9003fdd7f44246f94c535282e58436b3568a39734a76af8fac7b716a59b2; git remote add origin -- https://foo.bar.com/foo/bar.gitgo: foo.bar.com/foo/bar@v0.1.0: git remote add origin -- https://foo.bar.com/foo/bar.git in /root/goforge/pkg/mod/cache/vcs/e8ae9003fdd7f44246f94c535282e58436b3568a39734a76af8fac7b716a59b2: exit status 128:fatal: remote origin already exists.
瞧,go get -x 会帮你把每个步骤的操作都打出来。显然这个问题确实由 git 引起。大概是 go get 拉取模块时,会先创建一个裸仓库,然后 add origin 。问题就发生在 add origin 这步,git 认为已经有一个 origin 存在了。这是为啥呢?不妨去手动复现下:
$ mkdir git_test$ cd git_test$ git init$ git remote add origin -- https://foo.bar.com/foo/bar.gitfatal: remote origin already exists.
继续看看,现在这个仓库的 origin 是啥:
$ git remote -vorigin
看到这里,突然想起我的 git 配置了 origin 默认指向 HEAD:
$ git config -l | grep originbranch.master.remote=originremote.origin.push=HEAD
git 2.20 以上已经在一个空的 origin 上继续 add 了
删除了这个选项后:git config --global --unset remote.origin.push, 终于可以拉取成功了。
replace
试想这么一种场景,假设有个项目 foo 依赖 a 的 v1.0.0 版本,而 foo 依赖的 b 依赖了 a 的 v2.0.0 版本。那么这个时候 foo 的 mod 其实最终会依赖 a 的 v2.0.0 版本。如果你需要 foo 强行依赖 v1.0.0 版本,这个时候就派上了 replace 的上场。直接修改 go.mod 文件,添加:replace a => a v1.0.0 即可。
但是实际情况,往往会更复杂。比如 b 需要 a v2.0.0 的一些新特性的话,简单的 replace 往往不能解决这个问题。如何解决呢?自己去处理。比如,Kuma[1] 自己维护了一个 vendored 文件夹 replace 到本地来处理这种问题;Kubernetes[2] 也有个自己的 staging。
mod cache
需要注意的是 go 1.12 版本之前,mod cache 并不是并发安全的,同一个环境并发构建可能会产生竞态。而最新的 go 1.15 还提供了环境变量 GOMODCACHE 来指定 mod cache 的位置,CI 工具可以利用这项加快构建速度。
引用链接
[1] Kuma: https://github.com/kumahq/kuma/blob/master/vendored/README.md[2] Kubernetes: https://github.com/kubernetes/kubernetes/blob/master/staging/README.md
推荐阅读
