Go 语言使用 Go Modules(go.mod) 来管理依赖

前几日系统性差不多读完了一本讲解 Go 语言的书籍,记录下几篇笔记,现在终于能够开始看看专题性的知识了。首先就是关于 Go 如何管理依赖的问题,Java 经历了最早逐个下载  jar  包,到现在用 maven 来描述项目依赖,及进行项目的构建。而 Go 的起点还是要高一些,从一开始就有  go get, go install 命令来从中心库下载依赖。但管理多版本依赖是个问题,也没有明确的方式来怎么描述一个项目的依赖。

因此也就产生过一些第三方的 Go 依赖管理

  1. Glide(使用 glide.yaml 文件描述依赖)
  2. Godep (长时间没维护的项目)
  3. govendor (不再维护了,在 Go modules 出来后也不被推荐使用)
  4. 还有其他的 GPM, gb, gom, gvt 等都相继退出了历史的舞台

也就是说 Go Modules 才是王道,在理解 Go Modules 之前也许有必要了解一下传统 GOPATH 管理依赖的方式,其他第三方的管理工具用不着去学习了,早已成了云烟。

Go 语言首次在  1.11 版本中引进了 Go Modules, 在这之前或者没有启用 GO111MODULE 的话,我们用 go get 或 go install 来下载依赖,编译器会从 GOPATH 和 vendor 文件夹中查找包。

GOPATH 管理 Go 依赖

如果我们用的 1.11 之前的 Go 版本或者 GO111MODULE=off, 没有启用  Go Modules, 来看下 go get 下载的包会放到哪里。

go env 看到的默认值为 GO111MODULE="", 从此不能判断出是 on 还是 off, 每个版本的 GO 的 GO111MODULE 默认值是不同的,比如 Go 1.16 中默认为 on。我们以 Go 1.16(当前 Go 版本为 1.17, 在 2021-08-16 发布的) 为例,并且用 go env -w GO111MODULE=off 明确关掉 Module 的支持。也可以用系统环境变量 export GO111MODULE=off 来关闭,这比 go env -w 方式优先级更高。

Mac OS X 中刚安装后 Go 后,用 go env | grep GOPATH 查看到它指向 $HOME/go 下,如 /Users/yanbin/go,那么来运行一个 go get 命令,在运行它之前把 $HOME/go 目录清空

$ go get github.com/labstack/echo

它会下载 echo 及相关的依赖, 下载的过程中没有任何信息显示,完后会在 $HOME/go 下产生目录 pkg, src, 用 tree 命令显示 3 层来看下

注意:下载文件的位置是在 $GOPATH/pkg/darwin_amd64 和 $GOPATH/src/github.com 下,将和后面进行对比

这些就是我们执行 go get 产生的目录和文件,再我们运行 go buildgo run 等的时候就 Go 编译器就会从  $GOPATH 目录中读取依赖代码。

我们来测试一下 echo, 创建一个文件 server.go, 内容如下

运行 go run server.go, 看到启动界面

如果我们设置自己的 GOPATH, 比如用命令

$ export GOPATH=/Users/yanbin/test

再用 go env 可看过新的 GOPATH,如果用 go env -w GOPATH=/Users/yanbin/test 设置 GOPATH 将会被保存到 /Users/yanbin/Library/Application Support/go/env 文件中而变成全局的。 

清空掉 $HOME/go 目录,然后再执行

$ go get github.com/labstack/echo

同样是悄无声息的在下载依赖,完后它们在新的 GOPATH 目录中

相同的目录结构。

如果设置 GOPATH 为 ; (Windowns) 或 :(Linux) 号分隔的多个目录了,go get 会安装到第一个目录中。我们这里可以想像一下,如果多个项目的依赖全部放在 $HOME/go 同一个目录中,依赖不同版本就会有问题(go get 只从  github.com 的 master 分支下载,不能指定版本),如果每个项目都放覆盖 GOPATH, 那势必不停的重复下载相同的依赖,想要部分共享得为 GOPATH 设置多个目录,还不得不在执行 go get 前留意 $GOPATH 中第一个目录是什么。

Go Modules 管理依赖

现在来看下用 Go Modules 怎么处理依赖的,先执行 go env -w GO111MODULE=on 启用  Go Modules(Go 1.16 默认已开启),清空前面用  go get 安装的内容,在项目目录中直接创建 server.go, 内容与上相同。

然后执行

$ go mod init testgo

生成 go.mod 文件,内容为

接着执行 go mod tidy 命令, 下载完依赖后就能 go run server.go 启动了

go mod tidy 更新了 go.mod 文件为

并创建了 go.sum 文件记录了每一个所下载依赖的哈稀码。要增加新依赖的话可以直接编辑 go.mod 文件,以后构建或运行时就会下载对应的依赖。go mod tidy 能扫描代码收集所用到的依赖更新 go.mod 文件,并去除不再用的依赖。

Go 1.15 或之前的 go run server.go 会自动下载依赖并更新 go.mod 和 go.sum 文件,Go 1.16 的 go run 不会更新这两文件,而要用  go mod tidy 命令。但 Go 1.16 下在 go.mod 中配置了依赖,执行 go run server.go 会依照 go.mod 中的指示下载依赖。

现在再来看下启用了Go Module 之后文件载到了哪里,仍然是下载到了 $GOPATH 中了,只是目录结构稍有不同,也没有 src 目录

在 pkg 与 github.com 目录之间多了一个 mod, 还有一个新目录 sumdb,而且依赖包是带版本号的。

由此可对比出启用 GO111MODULE 与否下载的依赖的不同之处

  1. 未启用  GO111MODULE:pkg 下直接是 github.com 以及与平台相关的如  darwin_amd64 目录,有与 pkg 同级的 src 目录,依赖包没有版本号, 下载依赖时不显示信息
  2. 启用 GO111MODULE: pkg 与 github.com 之间有 mod 目录,没有与 pkg 同级的 src 目录,但有一个  sumdb 目录,依赖包有版本号,源代码分别在 pkg/mod/github.com 下每一个带版本的独自的目录中 

据以上的对比我们在 go env 显示的 GO111MODULE="" 时执行 go get 就能知道该版本的 Go 默认是否启用了 Module 功能。比如在  Go 1.16 中,由于默认 GO111MODULE=on, 所以即使用没有 go.mod 文件,执行 go get 命令也会按照启用了 Module 时规格来下载依赖,但执行 go run server.go 时还是会提示找不到 go.mod 文件。

server.go:5:3: no required module provides package github.com/labstack/echo: go.mod file not found in current directory or any parent directory; see 'go help modules'

查看默认是否启用了 Go Module

如果用 go env 看到的是明确的 GO111MODULE=offGO111MODULE=on 的话,我们很清是否启用了 Go Module。在 GO111MODULE=auto 时事情稍显得杂,如果默认时 GO111MODULE="" 为空时,那就依赖于当前版本的默认设定了。

有个最简单的方法来判断是否启用 Go Module, 即 go env | grep GMMOD= 看到是非空就是启用了 Go Module, 否则未启用。看下效果

$ ls
server.go vendor
$ go env -w GO111MODULE=""
$ go env | grep GOMOD=
GOMOD="/dev/null"                                 //默认启用了  gomod,但未找到  go.mod 文件
$ go env -w GO111MODULE=off
$ go env | grep GOMOD=
GOMOD=""
$ go env -w GO111MODULE=on
$ go env | grep GOMOD=
GOMOD="/dev/null"                                 //启用了 gomod 但没找到  go.mod 文件
$ go env -w GO111MODULE=auto
$ go env | grep GOMOD=
GOMOD=""                                                   //auto 时未找到 go.mod 视作未启用
$ go mod init testgo
go: creating new go.mod: module testgo
go: to add module requirements and sums:
go mod tidy
$ ls
go.mod server.go vendor
$ go env | grep GOMOD=
GOMOD="/Users/uqiu/testgo/go.mod"

上面是安装了 Go 1.16 后执行的,GO111MODULE="" 默认时也启用了 Go Module, GO111MODULE=auto 时只有创建了 go.mod 文件后才启用 Module. GOMOD 的值为空(未启用 gomod)与不为空(已启用 gomod)就会影响 go get 的下载行为,所以最终的决定因素是 gomod, GO111MODULE 只是一个手段。

关于 vendor 目录

Go Modules  有一个 go mod vendor 命令, 我们来看下它是做什么的。在用了 go mod tidy 后依赖下载到了 $GOPATH/pkg/mod 目录中,再执行下

$ go mod vendor

然后查看当前目录

在当前目录中生成了一个 vendor 目录,其下就是 github.com, 并且依赖中也没有版本号。看起来更象是为打包而设计的,目录结构也更接近于 GO111MODULE=off 时的样子。现在把 $GOPATH/go 目录清空掉

$ sudo rm -rf ~/go/*             // ~/go 是 $GOPATH 的路径

然后执行 go run server.go, 能正常执行,说明 Go 可以从 vendor 目录中加载依赖进行编译。

Vendor 目录是 Go 1.15 中加入除 GOPATH 和 GOROOT 之外的依赖查找解决方案,在 Go 1.15 中需设置 GO15VENDOREXPERIMENT=1 启用它,自 Go 1.16 后默认启用,所以 Go 查找依赖的完整顺序如下

  • 当前包下的 vendor 目录
  • 向上级目录查找,直到找到  src  下的  vendor 目录
  • 在 GOPATH 下查找依赖
  • 在 GOROOT 目录中查找依赖 

vendor 也只有在启用了 Go Module 功能后才能使用。

链接:

  1. go依赖包管理工具对比
  2. Go语言go mod包依赖管理工具使用详解
  3. go mod使用
  4. Why is GO111MODULE everywhere, and everything about Go Modules (updated with Go 1.16)
  5. 21. Go 语言中如何开源自己写的包给别人用?

类别: Go. 标签: . 阅读(83). 订阅评论. TrackBack.
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x