[TOC]
8、精通Golang项目依赖Go modules
一、什么是Go Modules?
Go modules 是 Go 语言的依赖解决方案,发布于 Go1.11,成长于 Go1.12,丰富于 Go1.13,正式于 Go1.14 推荐在生产上使用。
Go moudles 目前集成在 Go 的工具链中,只要安装了 Go,自然而然也就可以使用 Go moudles 了,而 Go modules 的出现也解决了在 Go1.11 前的几个常见争议问题:
- Go 语言长久以来的依赖管理问题。
- “淘汰”现有的 GOPATH 的使用模式。
- 统一社区中的其它的依赖管理工具(提供迁移功能)。
二、GOPATH的工作模式
Go Modoules的目的之一就是淘汰GOPATH, 那么GOPATH是个什么?
为什么在 Go1.11 前就使用 GOPATH,而 Go1.11 后就开始逐步建议使用 Go modules,不再推荐 GOPATH 的模式了呢?
(1) Wait is GOPATH?
$ go env GOPATH="/home/itheima/go" ...
go env
go ├── bin ├── pkg └── src ├── github.com ├── golang.org ├── google.golang.org ├── gopkg.in ....
GOPATH目录下一共包含了三个子目录,分别是:
.go$GOPATH/src/github.com/foo/bar
$GOPATH/srcgo get$GOPATH
(2) GOPATH模式的弊端
$GOPATH/src.go
go get
github.com/foo/bar
三、Go Modules模式
GOPATH/src
(1) go mod命令
命令 | 作用 |
---|---|
go mod init | 生成 go.mod 文件 |
go mod download | 下载 go.mod 文件中指明的所有依赖 |
go mod tidy | 整理现有的依赖 |
go mod graph | 查看现有的依赖结构 |
go mod edit | 编辑 go.mod 文件 |
go mod vendor | 导出项目所有的依赖到vendor目录 |
go mod verify | 校验一个模块是否被篡改过 |
go mod why | 查看为什么需要依赖某模块 |
(2) go mod环境变量
go env
$ go env GO111MODULE="auto" GOPROXY="https://proxy.golang.org,direct" GONOPROXY="" GOSUMDB="sum.golang.org" GONOSUMDB="" GOPRIVATE="" ...
GO111MODULE
GO111MODULE
- auto:只要项目包含了 go.mod 文件的话启用 Go modules,目前在 Go1.11 至 Go1.14 中仍然是默认值。
- on:启用 Go modules,推荐设置,将会是未来版本中的默认值。
- off:禁用 Go modules,不推荐设置。
可以通过来设置
$ go env -w GO111MODULE=on
GOPROXY
这个环境变量主要是用于设置 Go 模块代理(Go module proxy),其作用是用于使 Go 在后续拉取模块版本时直接通过镜像站点来快速拉取。
https://proxy.golang.org,direct
proxy.golang.org
如:
$ go env -w GOPROXY=https://goproxy.cn,direct
GOPROXY 的值是一个以英文逗号 “,” 分割的 Go 模块代理列表,允许设置多个模块代理,假设你不想使用,也可以将其设置为 “off” ,这将会禁止 Go 在后续操作中使用任何 Go 模块代理。
如:
$ go env -w GOPROXY=https://goproxy.cn,https://mirrors.aliyun.com/goproxy/,direct
direct
而在刚刚设置的值中,我们可以发现值列表中有 “direct” 标识,它又有什么作用呢?
实际上 “direct” 是一个特殊指示符,用于指示 Go 回源到模块版本的源地址去抓取(比如 GitHub 等),场景如下:当值列表中上一个 Go 模块代理返回 404 或 410 错误时,Go 自动尝试列表中的下一个,遇见 “direct” 时回源,也就是回到源地址去抓取,而遇见 EOF 时终止并抛出类似 “invalid version: unknown revision...” 的错误。
GOSUMDB
它的值是一个 Go checksum database,用于在拉取模块版本时(无论是从源站拉取还是通过 Go module proxy 拉取)保证拉取到的模块版本数据未经过篡改,若发现不一致,也就是可能存在篡改,将会立即中止。
sum.golang.org
goproxy.cnsum.golang.org
另外若对 GOSUMDB 的值有自定义需求,其支持如下格式:
++
也可以将其设置为“off”,也就是禁止 Go 在后续操作中校验模块版本。
GONOPROXY/GONOSUMDB/GOPRIVATE
这三个环境变量都是用在当前项目依赖了私有模块,例如像是你公司的私有 git 仓库,又或是 github 中的私有库,都是属于私有模块,都是要进行设置的,否则会拉取失败。
更细致来讲,就是依赖了由 GOPROXY 指定的 Go 模块代理或由 GOSUMDB 指定 Go checksum database 都无法访问到的模块时的场景。
而一般建议直接设置 GOPRIVATE,它的值将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以建议的最佳姿势是直接使用 GOPRIVATE。
并且它们的值都是一个以英文逗号 “,” 分割的模块路径前缀,也就是可以设置多个,例如:
$ go env -w GOPRIVATE="git.example.com,github.com/eddycjy/mquote"
设置后,前缀为 git.xxx.com 和 github.com/eddycjy/mquote 的模块都会被认为是私有模块。
如果不想每次都重新设置,我们也可以利用通配符,例如:
$ go env -w GOPRIVATE="*.example.com"
这样子设置的话,所有模块路径为 example.com 的子域名(例如:git.example.com)都将不经过 Go module proxy 和 Go checksum database,需要注意的是不包括 example.com 本身。
四、使用Go Modules初始化项目
(1) 开启Go Modules
$ go env -w GO111MODULE=on
又或是可以通过直接设置系统环境变量(写入对应的~/.bash_profile 文件亦可)来实现这个目的:
$ export GO111MODULE=on
(2) 初始化项目
创建项目目录
$ mkdir -p $HOME/aceld/modules_test $ cd $HOME/aceld/modules_test
执行Go modules 初始化
$ go mod init github.com/aceld/modules_test go: creating new go.mod: module github.com/aceld/modules_test
go mod initgithub.com/aceld/modules_testmain.go
package main import ( "fmt" "github.com/aceld/zinx/znet" "github.com/aceld/zinx/ziface" ) //ping test 自定义路由 type PingRouter struct { znet.BaseRouter } //Ping Handle func (this *PingRouter) Handle(request ziface.IRequest) { //先读取客户端的数据 fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) //再回写ping...ping...ping err := request.GetConnection().SendBuffMsg(0, []byte("ping...ping...ping")) if err != nil { fmt.Println(err) } } func main() { //1 创建一个server句柄 s := znet.NewServer() //2 配置路由 s.AddRouter(0, &PingRouter{}) //3 开启服务 s.Serve() }
aceld/modules_testgithub.com/aceld/zinxznetzifacezinx
$HOME/aceld/modules_test
$ go get github.com/aceld/zinx/znet go: downloading github.com/aceld/zinx v0.0.0-20200221135252-8a8954e75100 go: found github.com/aceld/zinx/znet in github.com/aceld/zinx v0.0.0-20200221135252-8a8954e75100
go.modgo.sum
(3) 查看go.mod文件
aceld/modules_test/go.mod
module github.com/aceld/modules_test go 1.14 require github.com/aceld/zinx v0.0.0-20200221135252-8a8954e75100 // indirect
我们来简单看一下这里面的关键字
module
go
require
// indirectgo get"github.com/aceld/zinx/znet""github.com/aceld/zinx/ziface"github.com/aceld/zinx
(4) 查看go.sum文件
在第一次拉取模块依赖后,会发现多出了一个 go.sum 文件,其详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。
github.com/aceld/zinx v0.0.0-20200221135252-8a8954e75100 h1:Ez5iM6cKGMtqvIJ8nvR9h74Ln8FvFDgfb7bJIbrKv54= github.com/aceld/zinx v0.0.0-20200221135252-8a8954e75100/go.mod h1:bMiERrPdR8FzpBOo86nhWWmeHJ1cCaqVvWKCGcDVJ5M= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
我们可以看到一个模块路径可能有如下两种:
h1:hash情况
github.com/aceld/zinx v0.0.0-20200221135252-8a8954e75100 h1:Ez5iM6cKGMtqvIJ8nvR9h74Ln8FvFDgfb7bJIbrKv54=
go.mod hash情况
github.com/aceld/zinx v0.0.0-20200221135252-8a8954e75100/go.mod h1:bMiERrPdR8FzpBOo86nhWWmeHJ1cCaqVvWKCGcDVJ5M= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
h1 hash 是 Go modules 将目标模块版本的 zip 文件开包后,针对所有包内文件依次进行 hash,然后再把它们的 hash 结果按照固定格式和算法组成总的 hash 值。
而 h1 hash 和 go.mod hash 两者,要不就是同时存在,要不就是只存在 go.mod hash。那什么情况下会不存在 h1 hash 呢,就是当 Go 认为肯定用不到某个模块版本的时候就会省略它的 h1 hash,就会出现不存在 h1 hash,只存在 go.mod hash 的情况。
五、修改模块的版本依赖关系
zinx v0.0.0-20200221135252-8a8954e75100zinx v0.0.0-20200306023939-bc416543ae24
zinx
$HOME/aceld/modules_test
$ go get github.com/aceld/zinx/znet go: downloading github.com/aceld/zinx v0.0.0-20200306023939-bc416543ae24 go: found github.com/aceld/zinx/znet in github.com/aceld/zinx v0.0.0-20200306023939-bc416543ae24 go: github.com/aceld/zinx upgrade => v0.0.0-20200306023939-bc416543ae24
v0.0.0-20200306023939-bc416543ae24
然后,我么看一下go.mod
module github.com/aceld/modules_test go 1.14 require github.com/aceld/zinx v0.0.0-20200306023939-bc416543ae24 // indirect
go getrequire
zinx
$GOPATH/pkg/mod/github.com/aceld
/go/pkg/mod/github.com/aceld$ ls zinx@v0.0.0-20200221135252-8a8954e75100 zinx@v0.0.0-20200306023939-bc416543ae24
/aceld/modules_testzinx@v0.0.0-20200306023939-bc416543ae24zinx@v0.0.0-20200306023939-bc416543ae24
/aceld/modules_test
$ go mod edit -replace=zinx@v0.0.0-20200306023939-bc416543ae24=zinx@v0.0.0-20200221135252-8a8954e75100
然后我们打开go.mod查看一下
module github.com/aceld/modules_test go 1.14 require github.com/aceld/zinx v0.0.0-20200306023939-bc416543ae24 // indirect replace zinx v0.0.0-20200306023939-bc416543ae24 => zinx v0.0.0-20200221135252-8a8954e75100
replace