接触 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 env -w$HOME/.config/go/env
mod
1234567
>go help modGo mod provides access to operations on modules.Note that support for modules is built into all the go commands,not just 'go mod'. For example, day-to-day adding, removing, upgrading,and downgrading of dependencies should be done using 'go get'.See 'go help modules' for an overview of module functionality.
大意渣翻:
注意对 modules 的支持已经内建在所有 go 子命令内,而不仅仅是 ‘go mod’ 。
举例说,每天添加、移除、升级、降级依赖,都应该使用 ‘go get’ 完成。
go get
踩坑
坑01:GOPROXY 特定情况不起效
go mod init Gopkg.tomlGopkg.lockgolang.org/x/
12345678910
>go mod init myappgo: creating new go.mod: module myappgo: copying requirements from Gopkg.lockgo: converting Gopkg.lock: stat github.com/360EntSecGroup-Skylar/excelize@v2.0.0: github.com/360EntSecGroup-Skylar/excelize@v2.0.0: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2go: converting Gopkg.lock: stat golang.org/x/text@v0.3.0: unrecognized import path "golang.org/x/text" (https fetch: Get https://golang.org/x/text?go-get=1: dial tcp 216.239.37.1:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time,or established connection failed because connected host has failed to respond.)go: converting Gopkg.lock: stat gopkg.in/russross/blackfriday.v2@d3b5b032dc8e8927d31a5071b56e14c89f045135: gopkg.in/russross/blackfriday.v2@v2.0.1: invalid version: go.mod has non-....v2 module path "github.com/russross/blackfriday/v2" at revision v2.0.1go: converting Gopkg.lock: stat golang.org/x/net@9b4f9f5ad5197c79fd623a3638e70d8b26cef344: unrecognized import path "golang.org/x/net" (https fetch: Get https://golang.org/x/net?go-get=1: dial tcp 216.239.37.1:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.)go: converting Gopkg.lock: stat golang.org/x/image@61b8692d9a5c9886248d7f96e0ba50ad77baab4c: unrecognized import path "golang.org/x/image" (https fetch: Get https://golang.org/x/image?go-get=1: dial tcp 216.239.37.1:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.)
go.modgo mod tidy
go.mod 只需要包含 module path 和 go 版本即可:
123
module myappgo 1.13
坑02:import 和 module path 不一致
go mod tidy
1234567
>go mod tidygo: finding github.com/mojocn/base64Captcha latest......go: myapp/model/data importsgithub.com/go-xorm/core: github.com/go-xorm/core@v0.6.3: parsing go.mod:module declares its path as: xorm.io/corebut was required as: github.com/go-xorm/core
这个好解决,把项目里对这个 module 的引用,都指向它声明的路径即可。
下面梳理一下出错的原因:
xorm.io/core
github.com/go-xorm/corevendor/github.com/go-xorm/core
xorm.io/core$HOME/go/pkg/mod/
退一步讲,如果将来 xorm.io 因为某些原因不再提供访问,而 github 那份还在,可以在 go.mod 通过 replace 关键字将下载地址指向 github,但其余的路径,依然要跟声明的路径保持一致(主要是 import 路径,下载保存是自动的,并不需要人工干预)。
坑03:最新版本 module 不兼容
go.modgo.sum
12345678
>make buildgo build -ldflags '-s -w'go: finding gopkg.in/yaml.v2 v2.2.2// 省略若干行...# myapp/model/datamodel\data\csv.go:59:6: xng.QuoteStr undefined (type *xorm.Engine has no field or method QuoteStr)model\data\csv.go:61:6: xng.QuoteStr undefined (type *xorm.Engine has no field or method QuoteStr)make: *** [Makefile:28: build] Error 2
Engine.QuoteStr()
go.modGopkg.toml
Gopkg.toml
1
>go mod edit -require=github.com/go-xorm/xorm@v0.7.1
这样就可以指定依赖的版本。如果你觉得敲命令太麻烦,直接手动改 go.mod 也可以。一般不推荐直接改,因为你的修改会在下次更新时被覆盖,唯独版本信息是会保留的。
改完再 tidy 一次。
坑04:module path 不统一
再编译一次:
12345678
>make buildgo build -ldflags '-s -w'go: finding github.com/go-xorm/xorm v0.7.1// 省略若干行...# myapp/model/datamodel\data\engine.go:117:17: cannot use level (type "xorm.io/core".LogLevel) as type "github.com/go-xorm/core".LogLevel in argument to xng.SetLogLevelmake: *** [Makefile:28: build] Error 2
还是 xorm 的错误。还记得我们在 坑02 中的修改吗,在 go mod 底下,要统一按照声明路径去 import。
github.com/go-xorm/corexorm.io/core
为了跟 v0.7.1 的 xorm 兼容,必须使用 < v0.6.3 的 core —— 实际上直接使用 v0.6.0 是最保险的。因为回退到了修改 module path 之前的版本,所以 坑02 的修改白改了,回退掉。
当然记录 坑02 仍然有意义,它提醒我 有时声明的 module path 未必和仓库地址一致 。
跟上面类似,core 包改回对应的 v0.6.0 ,重新 tidy。
坑05:主版本号变更
再一次编译:
1234567
>make buildgo build -ldflags '-s -w'# myapp/apiapi\survey.go:189:9: assignment mismatch: 2 variables but ex.GetCellValue returns 1 values// 省略若干相似的错误...api\survey.go:216:9: too many errorsmake: *** [Makefile:28: build] Error 2
依然是版本的兼容问题。
不过这次的错误跟前面的比,是反过来的:我的代码引用的是最新的 v2 代码,这在原来 dep 下是不需要区分包名的。但在 mod 里,大于 1 的大版本是需要体现在路径里的。
在 module 眼里,主版本号不同,相当于两个不同的 module。 这是因为根据 SemVer 的约定,大版本号的改变,意味着引入了 breaking changes。那么如果很不巧地,代码 直接 / 间接 依赖同一个包的不同大版本时,mod 是可以同时导入的,就不会存在依赖上的冲突。
github.com/360EntSecGroup-Skylar/excelizegithub.com/360EntSecGroup-Skylar/excelize/v2
临了 Mark 一篇文章 《再探go modules:使用与细节》。这篇文章对于 go mod 的一些细节做了分析,虽然发表于 1.12 发布前,但是现在来看仍然有效。

本文为本人原创,采用知识共享 “署名-非商业性使用-相同方式共享” 4.0 (CC BY-NC-SA 4.0)”许可协议进行许可。
本作品可自由复制、传播及基于本作品进行演绎创作。如有以上需要,请留言告知,在文章开头明显位置加上署名(Jayce Chant)、原链接及许可协议信息,并明确指出修改(如有),不得用于商业用途。谢谢合作。
详情请点击查看协议具体内容。