暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

面试题:如何更新 Go Module 项目的依赖 — 解决问题的方法很重要

polarisxu 2021-07-26
7678

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

关于 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)查看有更新的直接依赖项的方法

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。



觉得不错,欢迎关注:

点个在看是最大的支持

文章转载自polarisxu,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论