七叶笔记 » golang编程 » 面试题:如何更新 Go Module 项目的依赖

面试题:如何更新 Go Module 项目的依赖

这篇文章来自知识星球球友的问题:

问题描述可能不严谨,但大概意思是希望能够将项目的所有依赖都升级到最新版本(兼容的)。这里我引申一下,有如下 3 个问题:

  • 如何只更新直接依赖;
  • 如何只更新间接依赖;
  • 如何更新所有依赖;

这里不想直接给出答案,而是想和大家一起探讨下遇到类似这样的问题怎么找到答案,以及借此对相关知识点有一个更全面、深入的了解。

怎么寻找答案

对于 Go 来说,遇到问题(特别基础的问题除外),我认为应该优先去 golang-nuts 邮件组搜索。比如这个问题通过关键词 go mod update direct dependencies` 搜索。(此外,还可以尝试在 go 仓库的 issue 中搜索)

第 1、2 个就是相关的。查看这两个帖子,总结解决该问题大概的方法如下:

1)查看有更新的直接依赖项的方法

 go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}{{end}}' -m all  

该方法还有变种,比如查看更新的版本信息:

 go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}: {{.Version}} -> {{.Update.Version}}{{end}}' -m all  

你可以找一个仓库试试,比如 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
 go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}{{end}}' -m all | xargs go get -u  
  • 使用 go get -u $() 这种方式
 go get -u $(go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}{{end}}' -m all)  

回到我们开头的问题,根据 .Indirect 来进行分别处理即可。不过,对于更新项目所有的依赖,有一个更简便的方法,那就是直接 go get -u ./…。

当然,其实大部分问题,官方文档都会有相关的说明,只是可能你对文档不熟悉。

知识点学习

以上涉及到的知识点主要是 go list 命令的使用。Go 命令的学习,最权威的文档自然是官方文档。

 $ go help list  

以上命令可以查看 go list 的官方文档,命令使用方式:

 go list [-f format] [-json] [-m] [list  flags ] [build flags] [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

 type Module struct {
    Path      string       // module path
    Version   string       // module version
    Versions  []string     // available module versions (with -versions)
    Replace   *Module      // replaced by this module
    Time      *time.Time   // time version was created
    Update    *Module      // available update, if any (with -u)
    Main      bool         // is this the main module?
    Indirect  bool         // is this module only an indirect dependency of main module?
    Dir       string       // directory holding files for this module, if any
    GoMod     string       // path to go.mod file used when loading this module, if any
    GoVersion string       // go version used in module
    Error     *ModuleError // error loading module
}  

通过命令 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 模板的语法,可以写出如下命令:

 go list -m -f '{{if not (or .Main .Indirect)}}{{.Path}}{{end}}' all  

但结果包含了没有更新的依赖。有没有可用更新可以通过 Module 结构的 Update 字段判断,但需要加上 -u 选项:

 go list -m -u -f '{{if and (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 在 如何升级和降级依赖关系 文档中,介绍了一个命令:

 go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}: {{.Version}} -> {{.Update.Version}}{{end}}' -m all 2> /dev/null  

它查看直接依赖项的可用升级。然而,过程不可控,即我们不能通过它方便的更新某些依赖项。

此工具旨在通过交互的方式,使更新多个依赖项变得更加容易。这类似于 yarn upgrade-interactive ,但适用于 Go。

安装

 $ go get -u github.com/oligot/go-mod-upgrade  

使用

在使用模块的 Go 项目中,你现在可以运行:

 $ go-mod-upgrade  

这样就会出现类似上图的界面。其中颜色有助于标识更新类型:

  • 绿色进行较小的更新(minor update)
  • 黄色,用于补丁更新(patch)
  • 红色表示预发行更新(prerelease)

交互界面中,通过空格键选中某个包,上下箭头移动待选择包,还支持直接输入进行包的过滤,比如下图的 gjson 就是输入的。选中后回车,就会开始更新。

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

相关文章