接触 golang 很晚,实际用来开发大概在 1.9 左右,所以我的主要印象是在 1.9 、 1.10 上的,依赖管理经过一些尝试之后,选择了 『官方』(后来实际被抛弃了)的 dep(《golang 依赖管理:glide 从入门到放弃》)。
后来 1.11、1.12 推出了 module (亦即 go mod 命令),考虑到尚不稳定又有切换成本,就继续留守在 vendor 目录上。
2019 年 9月终于 1.13 出来了,做了几个比较大的改动,同时 module 也终于转正,所以我终于下定决心迁移到 1.13,并改用 mod 做依赖管理。
概要
迁移过程主要参考了《干货满满的 Go Modules 和 goproxy.cn》(实操主要是 #快速迁移项目至 Go Modules 部分),讲得非常清楚,也推荐大家参考,一些细节就不再赘述,只强调我踩坑的地方。
简单来说是这么个过程:
卸载原有的 go,并下载安装 1.13 版本。
官网 golang.org 因为是谷歌的服务器,也在屏蔽之列,部分同学可能连访问这个都有困难,其实国内有官方的镜像站 golang.google.cn 。
用
go env -w
重新设置你的环境变量 。注意 go env 的内容保存在
$HOME/.config/go/env
,不会覆盖原来的系统环境变量。在读取环境变量时, go env 的值优先。为了避免后续增加判断环境变量的负担,建议 go env 里有的、只有 go 会读取的环境变量,在系统环境变量里删除。因为国情特殊,一定要设置 GOPROXY。
针对依赖工具和项目情况迁移(以下主要讲这部分的坑)。因为我之前用的是 dep,下面全部是关于从 dep 的迁移。
特别提一下一个细节,在安装好的 1.13 下获取 mod
子命令的帮助,下面有一段提醒:
|
|
大意渣翻:
注意对 modules 的支持已经内建在所有 go 子命令内,而不仅仅是 ‘go mod’ 。
举例说,每天添加、移除、升级、降级依赖,都应该使用 ‘go get’ 完成。
也就是说所有跟依赖管理相关的命令,譬如 go get
,都是用新逻辑在处理。
踩坑
坑01:GOPROXY 特定情况不起效
本来直接在项目根目录敲 go mod init <mod_path>
,是可以自动从 Gopkg.toml
和 Gopkg.lock
导入依赖信息,自动完成迁移的。但是在国内直接这样做是会出错的,部分包获取超时(留意 golang.org/x/
开头的包):
|
|
这其实是一个 bug,init 导入依赖部分没有引用 GOPROXY。已经有人向官方提交了 issue,只是修复不知道要等到哪个版本合入。当前可以创建一个空白的 go.mod
然后执行 go mod tidy
来绕过。
go.mod 只需要包含 module path 和 go 版本即可:
|
|
坑02:import 和 module path 不一致
增加了 go.mod 之后,执行 go mod tidy
:
|
|
这个好解决,把项目里对这个 module 的引用,都指向它声明的路径即可。
下面梳理一下出错的原因:
xorm.io/core
这个 module,同时在 github.com 和 xorm.io 都有提供访问。
在使用 dep 的时候,从哪个路径 import,都是可行的。当我从 github.com/go-xorm/core
import 时,dep 就从 github 下载,并保存到 vendor/github.com/go-xorm/core
下。到编译的时候,到 vendor 下对应的路径去找。也就是无论选哪个,下载、保存、import 的路径,三者是对应的就行。
但是 mod 会读取 module 的 go.mod,它自称是 xorm.io/core
,那么从 github import 就是非法的。也就是现在要 加上跟 module 自身 go.mod 声明的路径一致。(当然,现在保存不在 vendor 目录了,而是在 $HOME/go/pkg/mod/
底下,从原来每个项目存一份,变成每个 module 的每个版本,全局只存一份。)
退一步讲,如果将来 xorm.io 因为某些原因不再提供访问,而 github 那份还在,可以在 go.mod 通过 replace 关键字将下载地址指向 github,但其余的路径,依然要跟声明的路径保持一致(主要是 import 路径,下载保存是自动的,并不需要人工干预)。
坑03:最新版本 module 不兼容
好了,依赖的分析和获取终于不报错了,go.mod
和 go.sum
也成功生成了。接下来让我们编译一下:
|
|
Engine.QuoteStr()
是一个返回当前数据库引擎使用的引号的方法,我当时特意使用这个方法,用来同时兼容不同的数据库,避免额外的判断。所以我确定这个方法是存在的。
这很容易想到是版本兼容性的问题。查看 go.mod
里的是 v0.7.9,再翻看 Gopkg.toml
之前用的是 v.0.7.1 。坑爹的是,新版本居然删掉了这个方法。
这里稍微提一下 版本号的问题 。go mod 强制使用 SemVer(如果不知道什么是 SemVer,看这篇 语义化版本 2.0),默认大版本没有改动的话,一定是兼容修改。所以会自动获取当前大版本下最新的版本,并不会参考 Gopkg.toml
的版本。不过话说回来, 即使按照 SemVer 的语义,也没办法埋怨 xorm 的团队,1.0 之前的版本默认为不稳定版本,没有义务保持兼容。
|
|
这样就可以指定依赖的版本。如果你觉得敲命令太麻烦,直接手动改 go.mod 也可以。一般不推荐直接改,因为你的修改会在下次更新时被覆盖,唯独版本信息是会保留的。
改完再 tidy 一次。
坑04:module path 不统一
再编译一次:
|
|
还是 xorm 的错误。还记得我们在 坑02 中的修改吗,在 go mod 底下,要统一按照声明路径去 import。
因为 坑03 ,xorm 改回了跟我代码兼容的 v0.7.1 ,那与之关联的 core 呢?跟上面类似地,看前后的配置,之前用的 v0.6.0 ,现在的是 v0.7.2 。关键的一点是,在 2019 年 6 月,在这两个版本之间的 v0.6.3,module path 从 github.com/go-xorm/core
改成了 xorm.io/core
, xorm 对它的引用在那个时间也做了相应的修改。
为了跟 v0.7.1 的 xorm 兼容,必须使用 < v0.6.3 的 core —— 实际上直接使用 v0.6.0 是最保险的。因为回退到了修改 module path 之前的版本,所以 坑02 的修改白改了,回退掉。
当然记录 坑02 仍然有意义,它提醒我 有时声明的 module path 未必和仓库地址一致 。
跟上面类似,core 包改回对应的 v0.6.0 ,重新 tidy。
坑05:主版本号变更
再一次编译:
|
|
依然是版本的兼容问题。
不过这次的错误跟前面的比,是反过来的:我的代码引用的是最新的 v2 代码,这在原来 dep 下是不需要区分包名的。但在 mod 里,大于 1 的大版本是需要体现在路径里的。
在 module 眼里,主版本号不同,相当于两个不同的 module。 这是因为根据 SemVer 的约定,大版本号的改变,意味着引入了 breaking changes。那么如果很不巧地,代码 直接 / 间接 依赖同一个包的不同大版本时,mod 是可以同时导入的,就不会存在依赖上的冲突。
把 import 里 github.com/360EntSecGroup-Skylar/excelize
改为 github.com/360EntSecGroup-Skylar/excelize/v2
,重新 tidy,这次编译就不再报错了,编译的结果也是可以正常运行的。到此,我踩的坑已经全部记录完毕。
临了 Mark 一篇文章 《再探go modules:使用与细节》。这篇文章对于 go mod 的一些细节做了分析,虽然发表于 1.12 发布前,但是现在来看仍然有效。
本文为本人原创,采用知识共享 “署名-非商业性使用-相同方式共享” 4.0 (CC BY-NC-SA 4.0)”许可协议进行许可。
本作品可自由复制、传播及基于本作品进行演绎创作。如有以上需要,请留言告知,在文章开头明显位置加上署名(Jayce Chant)、原链接及许可协议信息,并明确指出修改(如有),不得用于商业用途。谢谢合作。
详情请点击查看协议具体内容。