Go工程化--Go module
1. Go Module 简明教程
1.1 使用 Go Module
-
初始化
go mod init github.com/mohuishou/go-mod-example
执行上述命令会在当前目录下生成一个 go.mod 文件
-
go.mod
文件记录如下module github.com/mohuishou/go-mod-example go 1.16
有几点需要注意的是:
- 在 go get 的时候如果不手动指定版本信息,会自动拉取最新的版本的包
- 如果想要拉取指定版本可以通过
go get github.com/sirupsen/logrus@v1.7.0
的方式,支持@ 版本号
如@V1.7.0
@ 分支名
如@master
@commit tag
如@6cff360233dc4457f1536e4f3df4e4e740fd3410
// indirect
表示,我们在代码中没有直接应用这个包, 所谓间件引用是指,某个模块import xxx
时,内部间件引入了其他的模块
-
执行完 go get 命令之后还会在目录下生成一个 go.sum 文件
...... github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU= github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A= ......
这个文件主要包含当前依赖的所有的包,像
go-difflib
我们没有直接依赖,但是我们依赖的logrus
有依赖它,所以会列在这里h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
这一串是一个加密的哈希数据,用来保证这个版本一定是一致的,避免包的发布者删除了这个版本之后,修改代码重复发布 -
在代码中使用这个包
package main import "github.com/sirupsen/logrus" func main() { logrus.Info("hello") }
1.2 清理依赖
- 随着我们开发,可能会有一些包,之前依赖但是后面就不再依赖了,这个时候我们如果要清理哪些不再需要的依赖可以执行下面的命令来进行清理
go mod tidy
1.3 发布 Go Module
1.3.1 Go 版本号
go 默认使用 语义化 的版本来表示版本号,基本的方式
vMAJOR.MINOR.PATCH
- 有破坏性变更的时候需要增加主版本号,也就是
MAJOR
,例如v1.0.0
->v2.0.0
- 当有新增的函数或者是 API 时,我们增加
MINOR
版本号,例如v1.1.0
->v1.2.0
- 当没有新的
feature
的时候,例如bug
修复时,我们增加PATCH
版本号,例如v1.1.1 -> v1.1.2
- 有破坏性变更的时候需要增加主版本号,也就是
- 除此之外我们还可以在版本号后面使用 - 表示一些特殊的预发布版本例如
alpha beta
版本等v1.1.1-alpha
v1.1.1-beta2
- 上面这种特殊的版本只会在手动指定的版本号的时候才会去获取它,默认情况或者在更新版本的时候不会获取这些版本
- 为了兼容在
Go Module
出现之前的一些版本,你可能会看到还有一种比较特殊的版本号v0.0.0-20170915032832-14c0d48ead0c
- 如果依赖的仓库从来没有发布过版本,就会以这种方式存在
1.3.2 V1及之前版本发布
1.0 之前的版本发布非常简单,只需要做两件事情
- 添加一个
LICENSE
文件(非必须) - 使用
git tag v1.0.0
加一个版本 tag 即可
1.3.3 v2 及之后版本的发布
2.0 之后的版本发布就麻烦一些了,因为 Go Module 的限制,v2 之后的版本需要在 go.mod
中显示的指定 /v2
主版本来标识,用户在使用导入包的时候也必须加上这个版本标志
这个好处就是可以同时同时导入不同版本的包,但是在升级的时候就比较蛋疼了,必须要将所有文件的导入路径都修改一下
那么该如何发布新版本的包呢? 官方推荐的操作是
- 我们先在当前目录下创建一个 v2 文件夹,应为这样可以兼容那些还在使用 GOPATH 的用户,当然这不是必须的
- 然后我们再修改一下 go.mod 文件, 在包名后加上主版本号,例如
module github.com/mohuishou/go-mod-example/v2
- 最后我们再使用 git tag v2.0.0 打一个版本并发布即可
Go Module 避坑指南
2. Go Module 避坑指南
2.1 拉取依赖很慢,有的包还拉取不到
Go 默认的 GOPROXY
配置是 proxy.golang.org
, 默认国内无法访问,我们可以配置国内镜像,推荐 goproxy.cn
或者 goproxy.io
go env -w GOPROXY=https://goproxy.cn,direct
1.16 之前 Go Module 并未默认开启还需要配置
go env -w GO111MODULE=on
2.2 公司私有仓库包如何获取
Go 获取包的时候默认会走 PROXY
,这个只要你们的库没有对公网发布,那就获取不到,可以通过设置环境变量解决
go env -w GOPRIVATE=gitlab.com/xxx
2.3 依赖的包被自动升级
在 1.16 后, go build
, go test
, go get
等命令已经不会自动升级我们依赖的包了,但是在 1.16 之前,这个操作很难受。
首先,这个操作非常的反直觉,其次还有可能会导致非预期的 bug,虽然在 Go Module 的设计当中,主版本不变的情况下都应该保持向前兼容,但是很多知名的第三库都做不到这个,包括 Google 自己开发的 grpc,我们之前就出现过由于 grpc 版本自动升级导致的程序连接错误,必须要回退版本才行。
三个解决办法都可以解决:
- 升级 Go 版本到 1.16
- 使用
-mod=readonly
,例如go build -mod=readonly
- 在 go.mod 文件中使用
replace
指令指定版本
2.4 包的源代码仓库删库了怎么办?
这个其实在 Go Module
上还好一些,因为 Go Module 默认使用 Go Proxy
只要你使用的库的 LICENSE
和 GOPROXY
没有问题,一般都会有缓存
- 建议公司需要搭建自己的GOPROXY
- 建议使用官方的GOPROXU 或者是
goproxy.cn
2.5 logrus 包名问题
如下:
go: downloading github.com/Sirupsen/logrus v1.4.1
go get: github.com/Sirupsen/logrus@v1.0.6 updating to
github.com/Sirupsen/logrus@v1.4.1: parsing go.mod:
module declares its path as: github.com/sirupsen/logrus
but was required as: github.com/Sirupsen/logrus
现在这个错误应该比较少了,但是我们碰到过很多次了,主要的原因就是 logrus 的作者改了 github 名字,从 Sirupsen -> sirupse
n 就导致了大量依赖 logrus
库的第三方库报错冲突,这个的解决方案也是使用 replace
在 go.mod 的最后加上这么一句就可以了
replace (
github.com/Sirupsen/logrus v1.4.1 => github.com/sirupsen/logrus v1.4.1
)
2.6 go.sum git merge 冲突
其实很多包管理都有类似的问题,解决方法一般情况下 git merge 合并后再重新执行 go mod tidy 清理一下即可
2.7 Go 版本升级无法使用新特性
举个例子,我从 go 1.15 升级到 1.16 想使用 embed,也就是静态文件打包的特性,但是我们发现升级 Go 之后执行还是会报错
❯ go run ./main.go
# command-line-arguments
./main.go:9:3: go:embed requires go1.16 or later (-lang was set to go1.15; check go.mod)
这种情况修改一下 go.mod 文件中 go 1.15 到 go 1.16 即可