这篇文章来自知识星球球友的问题:
关于 Go 语言的 mod 引用问题,比如一个主项目,里面引用了其他人写的 lib1,lib2,lib3 等等,lib1 中又被lib2,lib3 引用,也可能互相引用,这样,当我更新 lib1 后,影响的 lib 就会很多,有没有办法在主项目中直接 go mod tidy 将所有 lib 都升级到最新版
问题描述可能不严谨,但大概意思是希望能够将项目的所有依赖都升级到最新版本(兼容的)。这里我引申一下,有如下 3 个问题:
- 如何只更新直接依赖;
- 如何只更新间接依赖;
- 如何更新所有依赖;
这里不想直接给出答案,而是想和大家一起探讨下遇到类似这样的问题怎么找到答案,以及借此对相关知识点有一个更全面、深入的了解。
怎么寻找答案
对于 Go 来说,遇到问题(特别基础的问题除外),我认为应该优先去 golang-nuts 邮件组搜索。比如这个问题通过关键词 go mod update direct dependencies` 搜索。(此外,还可以尝试在 go 仓库的 issue 中搜索)

第 1、2 个就是相关的。查看这两个帖子,总结解决该问题大概的方法如下:
1)查看有更新的直接依赖项的方法
golist-u-f'{{if(and(not(or.Main.Indirect)).Update)}}{{.Path}}{{end}}'-mall
该方法还有变种,比如查看更新的版本信息:
golist-u-f'{{if(and(not(or.Main.Indirect)).Update)}}{{.Path}}:{{.Version}}->{{.Update.Version}}{{end}}'-mall
你可以找一个仓库试试,比如 https://github.com/studygolang/studygolang 项目,结果如下:
code.gitea.io/sdk/gitea:v0.0.0-20191106151626-e4082d89cc3b->v0.12.0
github.com/PuerkitoBio/goquery:v1.5.0->v1.5.1
github.com/go-sql-driver/mysql:v1.4.1->v1.5.0
github.com/go-validator/validator:v0.0.0-20180514200540-135c24b11c19->v0.0.0-20200605151824-2b28d334fa05
github.com/jaytaylor/html2text:v0.0.0-20190408195923-01ec452cbe43->v0.0.0-20200412013138-3577fbdbcff7
github.com/labstack/echo/v4:v4.1.8->v4.1.16
github.com/polaris1119/config:v0.0.0-20160609095218-06a751e884f3->v0.0.0-20160628025248-e4f8b7e9e2ef
github.com/sundy-li/html2article:v0.0.0-20170724020440-d0b6c083441f->v0.0.0-20180131134645-09ac198090c2
github.com/tidwall/gjson:v1.3.2->v1.6.0
golang.org/x/net:v0.0.0-20190607181551-461777fb6f67->v0.0.0-20200625001655-4c5254603344
golang.org/x/oauth2:v0.0.0-20190226205417-e64efc72b421->v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/text:v0.3.2->v0.3.3
xorm.io/core:v0.7.2->v0.7.3
xorm.io/xorm:v0.8.0->v1.0.2
2)更新它们
已经找出了哪些需要更新,具体更新是通过 go get 命令,这也有两种方式:
- 使用 xargs
golist-u-f'{{if(and(not(or.Main.Indirect)).Update)}}{{.Path}}{{end}}'-mall|xargsgoget-u
- 使用 go get -u $() 这种方式
goget-u$(golist-u-f'{{if(and(not(or.Main.Indirect)).Update)}}{{.Path}}{{end}}'-mall)
回到我们开头的问题,根据 .Indirect 来进行分别处理即可。不过,对于更新项目所有的依赖,有一个更简便的方法,那就是直接 go get -u ./…。
当然,其实大部分问题,官方文档都会有相关的说明,只是可能你对文档不熟悉。
知识点学习
以上涉及到的知识点主要是 go list 命令的使用。Go 命令的学习,最权威的文档自然是官方文档。
nbsp;gohelplist
以上命令可以查看 go list 的官方文档,命令使用方式:
golist[-fformat][-json][-m][listflags][buildflags][packages]
该命令对于 GOPATH 和 Module 有些不同,这里忽略 GOPATH,只关注 Module 相关的内容,上面使用的几个命令行选项说明下:
选项说明-m针对使用了 Module 的项目,Module 项目必须的选项-u加上可用升级的信息-f进行格式化输出,使用 text/template 语法-json通过 json 格式输出
最后的 packages,可以指定多个 package,还有两个特殊的:
- all:表示当前项目所有的活跃(当前项目使用的)模块
- …:匹配特定模式的模块,比如 github.com/… 表示匹配所有 github.com 开头的模块
注:Go 命令中,all、... 和 std 一般有特殊用途
针对我们的问题,重点在于 -f ,因此需要了解一个结构:Module
typeModulestruct{
Pathstring//modulepath
Versionstring//moduleversion
Versions[]string//availablemoduleversions(with-versions)
Replace*Module//replacedbythismodule
Time*time.Time//timeversionwascreated
Update*Module//availableupdate,ifany(with-u)
Mainbool//isthisthemainmodule?
Indirectbool//isthismoduleonlyanindirectdependencyofmainmodule?
Dirstring//directoryholdingfilesforthismodule,ifany
GoModstring//pathtogo.modfileusedwhenloadingthismodule,ifany
GoVersionstring//goversionusedinmodule
Error*ModuleError//errorloadingmodule
}
通过命令 go list -m -json all 可以通过 json 格式查看依赖信息,类似这样:
...
{
"Path":"xorm.io/core",
"Version":"v0.7.2",
"Time":"2019-09-28T05:59:35Z",
"Dir":"/Users/xuxinhua/go/pkg/mod/xorm.io/core@v0.7.2",
"GoMod":"/Users/xuxinhua/go/pkg/mod/cache/download/xorm.io/core/@v/v0.7.2.mod"
}
{
"Path":"xorm.io/xorm",
"Version":"v0.8.0",
"Time":"2019-10-16T06:55:10Z",
"Dir":"/Users/xuxinhua/go/pkg/mod/xorm.io/xorm@v0.8.0",
"GoMod":"/Users/xuxinhua/go/pkg/mod/cache/download/xorm.io/xorm/@v/v0.8.0.mod",
"GoVersion":"1.11"
}
...
因此如果需要更新所有直接依赖的版本,需要先找出所有的直接依赖,根据以上结构加上 text/template 模板的语法,可以写出如下命令:
golist-m-f'{{ifnot(or.Main.Indirect)}}{{.Path}}{{end}}'all
但结果包含了没有更新的依赖。有没有可用更新可以通过 Module 结构的 Update 字段判断,但需要加上 -u 选项:
golist-m-u-f'{{ifand(not(or.Main.Indirect)).Update}}{{.Path}}{{end}}'all
找到了需要更新的依赖,然后就是更新了。这就需要使用到 go get 命令。关于这个命令,我留几个问题希望你能找到答案。以 github.com/tidwall/gjson 包为例,比如 studygolang 项目目前依赖的版本是 v1.3.2,而 v1.3.x 最新版本是 v1.3.6,v1.x.x 最新版本是 v1.6.0。
1)如何更新到 v1.3.6?
2)go get -u github.com/tidwall/gjson@none 是什么意思?
3)在 module 项目中,go get -u 和 go get -u ./... 有什么区别?
补充
通过这个问题和寻找答案的过程,还有其他收获:
1)有人建议 go get 可以将直接依赖和间接依赖分开更新。见 issue 28424。
2)有一个库用于更新依赖,它通过交互的方式进行,该库叫 go-mod-upgrade。
简单介绍下这个库。
交互式更新过期依赖

请注意,目前只支持补丁(patch)和次要版本更新(minor updates)。
为什么开发此库?
Go Wiki 在 如何升级和降级依赖关系 文档中,介绍了一个命令:
golist-u-f'{{if(and(not(or.Main.Indirect)).Update)}}{{.Path}}:{{.Version}}->{{.Update.Version}}{{end}}'-mall2>/dev/null
它查看直接依赖项的可用升级。然而,过程不可控,即我们不能通过它方便的更新某些依赖项。
此工具旨在通过交互的方式,使更新多个依赖项变得更加容易。这类似于 yarn upgrade-interactive ,但适用于 Go。
安装
nbsp;goget-ugithub.com/oligot/go-mod-upgrade
使用
在使用模块的 Go 项目中,你现在可以运行:
nbsp;go-mod-upgrade
这样就会出现类似上图的界面。其中颜色有助于标识更新类型:
- 绿色进行较小的更新(minor update)
- 黄色,用于补丁更新(patch)
- 红色表示预发行更新(prerelease)
交互界面中,通过空格键选中某个包,上下箭头移动待选择包,还支持直接输入进行包的过滤,比如下图的 gjson 就是输入的。选中后回车,就会开始更新。

不过这个工具没法控制升级 patch 还是 minor update。