文章506
标签266
分类65

Golang依赖管理gomod

随着go modules在go1.11版推出,go1.12版功能不断改进,再到go1.13版完善优化,Go Modules基本成为了官方推荐的包依赖管理工具了

本篇讲解了gomod的基本使用;

本文节选自: GO 依赖管理工具go Modules(官方推荐)


Golang依赖管理gomod

Go Modules是官方正式推出的包依赖管理项目,由Russ Cox (即Go 现在的掌舵人)推动;


对于Golang的另一个依赖管理工具Dep目前则处于“official experiment”阶段;

Go Modules特点

介绍

Go modules出现的目的之一就是为了解决 GOPATH 的问题,也就相当于是抛弃 GOPATH 了

[以前项目必须在$GOPATH/src里进行,现在Go允许在$GOPATH/src外的任何目录下使用 go.mod 创建项目]

随着模块一起推出的还有模块代理协议(Module proxy protocol),通过这个协议我们可以实现 Go 模块代理(Go module proxy),也就是依赖镜像


可以使用国内的代理下载相关的依赖;

在国内各种墙的背景下,有的时候go get能让你怀疑人生~


版本

Go Modules的Tag必须遵循语义化版本控制,如果没有则将忽略 Tag,然后根据你的 Commit 时间和哈希值再为你生成一个假定的符合语义化版本控制的版本号

Go modules 还默认认为,只要你的主版本号不变,那这个模块版本肯定就不包含Breaking changes(因为语义化版本控制就是这么规定的)


模块缓存

Global Caching这个主要是针对 Go modules 的全局缓存数据说明,如下:

  • 同一个模块版本的数据只缓存一份,所有其他模块共享使用
  • 目前所有模块版本数据均缓存在 $GOPATH/pkg/mod$GOPATH/pkg/sum下,未来或将移至 $GOCACHE/mod$GOCACHE/sum下( 可能会在当 $GOPATH 被淘汰后)
  • 可以使用 go clean -modcache 清理所有已缓存的模块版本数据

其他:

在 Go1.11 之后 GOCACHE 已经不允许设置为 off

这也是为了模块数据缓存移动位置做准备,因此大家应该尽快做好适配

如果你的版本是go1.12或更早版本,建议升级到go1.13,来体验一把go modules;

它能给你带来哪些方面身心的愉悦~


Go Modules相关操作

前提条件

安装或升级Golang到1.13+


配置环境变量

需要配置的变量主要有:

# 修改 GOBIN 路径(可选)
go env -w GOBIN=$HOME/bin
# 打开 Go modules
go env -w GO111MODULE=on
# 设置 GOPROXY
go env -w GOPROXY=https://goproxy.cn,direct

关于GOBIN

GOBIN 程序生成的可执行文件的路径,你可以设置它,也可以不设置;

默认是个空字符串, 它会链接距离最短的$GOPATH下的./bin文件夹;

这里多说一句:

go install才会链接到GOBIN;

go build之后你的可执行文件和你的main.go在同一目录下


关于go env -w

Go1.13新增了 go env -w 用于写入环境变量,而写入的地方是os.UserConfigDir所返回的路径

需要注意的是: go env -w 不会覆写,它不会覆盖系统环境变量


关于GO111MODULE

这个环境变量主要是 Go modules 的开关,主要有以下参数:

  • auto:只在项目包含了 go.mod 文件时启用 Go modules,在 Go 1.13 中是默认值,详见golang.org/issue/31857
  • on:任何情况下启用 Go modules,推荐设置,未来版本中的默认值(GOPATH 从此成为历史)
  • off:禁用 Go modules

关于GOPROXY

这个环境变量主要是用于设置 Go 模块代理

它的值是一个以英文逗号 “,” 分割的 Go module proxy 列表,默认是proxy.golang.org,国内访问不了

这里要感谢盛傲飞和七牛云为中国乃至全世界的 Go 语言开发者提供免费、可靠的、持续在线的且经过CDN加速Go module proxy(goproxy.cn

值列表中的 direct 为特殊指示符:

direct用于指示 Go 回源到模块版本的源地址去抓取(比如 GitHub 等)

  • 当值列表中上一个 Go module proxy 返回 404 或 410 错误时,Go 自动尝试列表中的下一个;
  • 在遇见direct时回源;
  • 遇见 EOF 时终止并抛出类似 invalid version: unknown revision…的错误

module-get和gopath-get

使用go.mod管理依赖会对go get命令产生一定影响


使用go help module-getgo help gopath-get 可分别获取Go modules启用和未启用两种状态下的 go get 的行为

在module-get模式下:

使用 go get 拉取新的依赖:

  • 拉取最新的版本(优先择取 tag):go get golang.org/x/text@latest
  • 拉取 master 分支的最新 commit:go get golang.org/x/text@master
  • 拉取 tag 为 v0.3.2 的 commit:go get golang.org/x/text@v0.3.2
  • 拉取 hash 为 342b231 的 commit,最终会被转换为 v0.3.2:go get golang.org/x/text@342b2e

使用 go get -u 更新现有的依赖;


项目测试

创建项目

这里我们在$GOPATH/src外,创建项目目录


注:

初始化gomod不可在$GOPATH路径下, 否则可能报错!

例如:

mkdir ~/go_learn
cd  ~/go_learn

新建main.go

package main

import (
    "github.com/gin-gonic/gin"
    "fmt"
)

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        fmt.Println("hello world!")
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}

在go_learn的根目录下生成go mod

go mod init go_learn

其中:

go_learn为你的模块名称;

由于gomod项目并不在$GOPATH下, 所以在引用本地模块时就需要使用到这个模块名

go.mod的内容类似于:

module go_learn

go 1.14

go.mod是启用了 Go moduels 的项目所必须的最重要的文件:

描述了当前项目(也就是当前模块)的元信息,每一行都以一个动词开头,目前有以下 5 个动词:

  • module:用于定义当前项目的模块路径
  • go:用于设置预期的 Go 版本
  • require:用于设置一个特定的模块版本
  • exclude:用于从使用中排除一个特定的模块版本
  • replace:用于将一个模块版本替换为另外一个模块版本

这里的填写格式基本为包引用路径+版本号


另外比较特殊的是go $version,目前从 Go1.13 的代码里来看,还只是个标识作用,暂时未知未来是否有更大的作用


构建项目

在go_learn根目录下执行go build:

D:\workspace\go_learn>go build
go: downloading github.com/gin-gonic/gin v1.6.3
go: downloading github.com/mattn/go-isatty v0.0.12
go: downloading github.com/gin-contrib/sse v0.1.0
go: downloading gopkg.in/yaml.v2 v2.2.8
go: downloading github.com/golang/protobuf v1.3.3
go: downloading github.com/go-playground/validator/v10 v10.2.0
go: downloading github.com/ugorji/go v1.1.7
go: downloading github.com/ugorji/go/codec v1.1.7
go: downloading github.com/leodido/go-urn v1.2.0
go: downloading github.com/go-playground/universal-translator v0.17.0
go: downloading github.com/go-playground/locales v0.13.0
......

此时会从GOPROXY开始依次尝试下载依赖包;

完成后项目目录结构如下:

 D:\workspace\go_learn>dir

2020/05/10  18:49    <DIR>          .
2020/05/10  18:49    <DIR>          ..
2020/05/10  18:28                71 go.mod
2020/05/10  18:28             4,360 go.sum
2020/05/10  18:49        14,727,680 go_learn.exe
2020/05/10  18:28               284 main.go

项目中增加了go.sum和二进制文件go_learn.exe;

go.sum文件内容:

github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
......

go.sum类似于比如dep的 Gopkg.lock的文件,它详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值以备 Go在今后的操作中保证项目所依赖的那些模块版本不会被篡改

我们可以看到一个模块路径可能有如下两种:

github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

前者为 Go modules 打包整个模块包文件 zip 后再进行 hash 值,而后者为针对 go.mod 的 hash 值

他们两者,要不就是同时存在,要不就是只存在 go.mod hash


注: 不存在zip hash而只存在gomod hash的情况

当Go认为肯定用不到某个模块版本的时候就会省略它的 zip hash,就会出现不存在 zip hash,只存在 go.mod hash 的情况

此外go.mod文件内容发生了变化,增加了:

require github.com/gin-gonic/gin v1.6.3

默认使用最新版本的package


更换依赖版本

查看gin所有历史版本:

go list -m -versions github.com/gin-gonic/gin 
github.com/gin-gonic/gin v1.1.1 v1.1.2 v1.1.3 v1.1.4 v1.3.0 v1.4.0 v1.5.0 v1.6.0 v1.6.1 v1.6.2 v1.6.3

如果想更换依赖版本,比如v1.5.0,怎么办?

只需执行如下命令:

# 修改依赖版本
go mod edit -require="github.com/gin-gonic/gin@v1.5.0"
# 更新现有依赖
go mod tidy 

@后跟版本号,这个时候go.mod已经修改好了

require github.com/gin-gonic/gin v1.5.0

其他命令

查看所有项目依赖的包

go list -m all
go_learn
github.com/davecgh/go-spew v1.1.1
github.com/gin-contrib/sse v0.1.0
github.com/gin-gonic/gin v1.6.3
github.com/go-playground/assert/v2 v2.0.1
github.com/go-playground/locales v0.13.0
github.com/go-playground/universal-translator v0.17.0
github.com/go-playground/validator/v10 v10.2.0
github.com/golang/protobuf v1.3.3
github.com/google/gofuzz v1.0.0
github.com/json-iterator/go v1.1.9
github.com/leodido/go-urn v1.2.0
github.com/mattn/go-isatty v0.0.12
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742
github.com/pmezard/go-difflib v1.0.0
github.com/stretchr/objx v0.1.0
github.com/stretchr/testify v1.4.0
github.com/ugorji/go v1.1.7
github.com/ugorji/go/codec v1.1.7
golang.org/x/sys v0.0.0-20200116001909-b77594299b42
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
gopkg.in/yaml.v2 v2.2.8

快速迁移项目至Go Modules

在你项目的根目录下执行 go mod init 项目名 (项目名可不加, 默认为父级目录名),以生成 go.mod 文件

执行 go mod tidy 更新整理现有的依赖,删除未使用的依赖


gomod命令小结

命令 说明
go mod download 下载 go.mod 文件中指明的所有依赖
go mod tidy 整理现有的依赖,删除未使用的依赖
go mod graph 查看现有的依赖结构
go mod init 生成 go.mod 文件
(Go 1.13 中唯一一个可以生成 go.mod 文件的子命令)
go mod edit 编辑 go.mod 文件
go mod vendor 导出现有的所有依赖
(事实上 Go modules 正在淡化 Vendor 的概念)
go mod verify 校验一个模块是否被篡改过
go clean -modcache 清理所有已缓存的模块版本数据
go mod 查看所有 go mod的使用命令

go mod vendor说明

最后想再说一下vendor:

使用goverdor来管理项目依赖包时, 如果GOPATH中本身没有项目的依赖包,则需要通过go get先下载到GOPATH中,再通过govendor add +external拷贝到vendor目录中

Go 1.6以上版本默认开启GO15VENDOREXPERIMENT环境变量

而在使用gomod管理依赖包时默认是不会下载到项目目录中的(如上例子使用go build后, 目录中并没有依赖文件)

个人觉得使用gomod管理依赖时有点像Java的Maven;


但是有时候还是需要将依赖加入到项目的, 比如:

构建的环境无法连接其他网络或者无法连接一些依赖库下载对应依赖时(尤其是国内这种恶劣的开发环境), 将依赖直接导出到项目目录直接进行构建就显得尤为重要了!

在上面的例子之上通过go mod verdor命令将依赖导入到项目目录:

D:\workspace\go_learn>go mod vendor
go: downloading golang.org/x/sys v0.0.0-20200116001909-b77594299b42

D:\workspace\go_learn>dir

2020/05/10  20:08                71 go.mod
2020/05/10  20:08             4,360 go.sum
2020/05/10  18:49        14,727,680 go_learn.exe
2020/05/10  18:28               284 main.go
2020/05/10  20:36    <DIR>          vendor

可以看到, 项目目录中多了一个verdor目录;

在提交代码时, 加入verdor目录, 则在构建部署时就无需再次拉取依赖了;

当然坏处就是整个项目变得特别臃肿~


附录

测试代码源码:

文章参考:

如果觉得文章写的不错, 可以关注微信公众号: Coder张小凯

内容和博客同步更新~



本文作者:Jasonkay
本文链接:https://jasonkayzk.github.io/2020/05/10/Golang依赖管理gomod/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可