如何调试 Go mod 的各种异常
Go mod 自从诞生之日就带来了太多太多的争议,当然不能否认它的设计初衷是好的。然而在调试其各种异常时,却浪费了太多开发者的时间。可以毫不客气的说,从来没有一种语言的版本管理,能让人如此崩溃。
本文记录了一些我的踩坑经验,希望能给还在挣扎中的 Gopher 一些帮助。
go get
先来看看最近我遇到的一个问题:
$ go mod tidy
go: 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.git
cd /root/goforge/pkg/mod/cache/vcs/e8ae9003fdd7f44246f94c535282e58436b3568a39734a76af8fac7b716a59b2; git init --bare
0.011s# cd /root/goforge/pkg/mod/cache/vcs/e8ae9003fdd7f44246f94c535282e58436b3568a39734a76af8fac7b716a59b2; git init --bare
cd /root/goforge/pkg/mod/cache/vcs/e8ae9003fdd7f44246f94c535282e58436b3568a39734a76af8fac7b716a59b2; git remote add origin -- https://foo.bar.com/foo/bar.git
0.006s# cd /root/goforge/pkg/mod/cache/vcs/e8ae9003fdd7f44246f94c535282e58436b3568a39734a76af8fac7b716a59b2; git remote add origin -- https://foo.bar.com/foo/bar.git
go: 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.git
fatal: remote origin already exists.
继续看看,现在这个仓库的 origin
是啥:
$ git remote -v
origin
看到这里,突然想起我的 git 配置了 origin
默认指向 HEAD
:
$ git config -l | grep origin
branch.master.remote=origin
remote.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
推荐阅读