前几日系统性差不多读完了一本讲解 Go 语言的书籍,记录下几篇笔记,现在终于能够开始看看专题性的知识了。首先就是关于 Go 如何管理依赖的问题,Java 经历了最早逐个下载 jar 包,到现在用 maven 来描述项目依赖,及进行项目的构建。而 Go 的起点还是要高一些,从一开始就有 go get
, go install
命令来从中心库下载依赖。但管理多版本依赖是个问题,也没有明确的方式来怎么描述一个项目的依赖。
因此也就产生过一些第三方的 Go 依赖管理
- Glide(使用 glide.yaml 文件描述依赖)
- Godep (长时间没维护的项目)
- govendor (不再维护了,在 Go modules 出来后也不被推荐使用)
- 还有其他的 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 层来看下
1 2 3 4 5 6 7 8 9 10 11 12 |
$ tree -L 3 -R ~/go /Users/yanbin/go ├── pkg │ └── darwin_amd64 │ └── github.com └── src ├── github.com │ ├── labstack │ ├── mattn │ └── valyala └── golang.org └── x |
注意:下载文件的位置是在 $GOPATH/pkg/darwin_amd64 和 $GOPATH/src/github.com 下,将和后面进行对比
这些就是我们执行 go get
产生的目录和文件,再我们运行 go build
或 go run
等的时候就 Go 编译器就会从 $GOPATH 目录中读取依赖代码。
我们来测试一下 echo, 创建一个文件 server.go, 内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main import ( "net/http" "github.com/labstack/echo" ) func main() { e := echo.New() e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "Hello, World!") }) e.Logger.Fatal(e.Start(":1323")) } |
运行 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 目录中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ tree -L 5 -R /Users/yanbin/go /Users/yanbin/go ├── pkg │ └── darwin_amd64 │ └── github.com │ └── labstack │ └── echo.a └── src ├── github.com │ ├── labstack │ │ ├── echo │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── _fixture │ │ │ ├── bind.go │ │ │ ├── bind_test.go .................. |
相同的目录结构。
如果设置 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
文件,内容为
1 2 3 |
module testgo go 1.16 |
接着执行 go mod tidy
命令, 下载完依赖后就能 go run server.go
启动了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ go mod tidy go: finding module for package github.com/labstack/echo go: downloading github.com/labstack/echo v1.4.4 go: downloading github.com/labstack/echo v3.3.10+incompatible ......... $ go run server.go ____ __ / __/___/ / ___ / _// __/ _ \/ _ \ /___/\__/_//_/\___/ v3.3.10-dev High performance, minimalist Go web framework https://echo.labstack.com ____________________________________O/_______ O\ ⇨ http server started on [::]:1323 |
go mod tidy
更新了 go.mod
文件为
1 2 3 4 5 6 7 8 9 10 |
module testgo go 1.16 require ( github.com/labstack/echo v3.3.10+incompatible github.com/labstack/gommon v0.3.0 // indirect github.com/stretchr/testify v1.7.0 // indirect golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect ) |
并创建了 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 目录
123456789101112131415161718192021222324 $ tree -L 5 -R /Users/yanbin/go/Users/yanbin/go└── pkg├── mod│ ├── cache│ │ ├── download│ │ │ ├── github.com│ │ │ ├── golang.org│ │ │ ├── gopkg.in│ │ │ └── sumdb│ │ └── lock│ ├── github.com│ │ ├── davecgh│ │ │ └── go-spew@v1.1.0│ │ ├── labstack│ │ │ ├── echo@v1.4.4│ │ │ ├── echo@v3.3.10+incompatible│ │ │ └── gommon@v0.3.0│ │ ├── mattn│ │ │ ├── go-colorable@v0.1.2............................└── sumdb└── sum.golang.org└── latest
在 pkg 与 github.com 目录之间多了一个 mod
, 还有一个新目录 sumdb
,而且依赖包是带版本号的。
由此可对比出启用 GO111MODULE 与否下载的依赖的不同之处
- 未启用 GO111MODULE:pkg 下直接是 github.com 以及与平台相关的如 darwin_amd64 目录,有与 pkg 同级的 src 目录,依赖包没有版本号, 下载依赖时不显示信息
- 启用 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=off
或 GO111MODULE=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/yanbin/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
然后查看当前目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ tree -L 5 -R ~. /Users/yanbin/testgo ├── go.mod ├── go.sum ├── server.go └── vendor ├── github.com │ ├── labstack │ │ ├── echo │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── bind.go │ │ │ ├── context.go ................. └── modules.txt |
在当前目录中生成了一个 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 功能后才能使用。
链接:
- go依赖包管理工具对比
- Go语言go mod包依赖管理工具使用详解
- go mod使用
- Why is GO111MODULE everywhere, and everything about Go Modules (updated with Go 1.16)
- 21. Go 语言中如何开源自己写的包给别人用?
本文链接 https://yanbin.blog/go-use-go-mod-manage-dependencies/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。