Go 包管理历史以及 Go mod 使用
之前也写过 Go 管理依赖工具 godep 的使用,当时看 godep 使用起来还是挺方便,其原因主要在于有总比没有强。关于依赖管理工具其实还是想从头聊聊这个需求以及大家做这个功能的各种出发点。
GOPATH 和 GOROOT
GOROOT 这个变量的作用就是为了告诉当前运行的 Go 进程当前 Go 安装在哪里,当你想要运行的时候去哪里找 Go SDK相关的类。
GOPATH 这个设定其实从语言层面上来说就有点反设计模式。主要原因在于 Go 刚出生的时候没有自带包管理功能,默认所有的项目和引用的第三方包都下载到 src 目录下,第三方包对于 Go 而言也是一个可用的Go项目,这一点跟 Java 区别还是挺大。
那 GOPATH 是否可以设定多个呢?当让可以,并且人家就是这么让你用的。
比如你有项目A,设定的 GOPATH 是:/src/projA/
,那么A项目所有的依赖都会下载在这个目录。
比如你有项目B,设定的 GOPATH 是:/src/projB/
,那么B项目所有的依赖都会下载在这个目录。
而如果你使用同一个 GOPATH,那么你所有的项目 down 下来的依赖都在 src 目录下,这时候就有版本的问题,如果 A 项目想用 依赖 C 的 1.0.0 版本,B 项目想用依赖 C 的 1.0.1 版本,这时候该怎么指定呢?
正确的 GOPATH 就是一个项目一个。
包管理历史上的“恩怨情仇”
所以问题来了,一个项目一个 GOPATH,如果我有三个项目是个超级无敌大系统,可能会 down 下来整个 github 上的开源库,那这存储都是问题啊。
针对 Go 官方对包管理的乱象无动于衷,社区的同学们实在是忍无可忍,相继开源了各种包管理工具。
13 年的时候 Godep 诞生,原理很简单,跟 maven 一样,指定一个统一的包管理目录,将这个目录作为唯一的 GOPATH。也是在这一年 Docker 开始火起来了, Go 作为微服务开发“非官方指定语言”被广泛使用,同时矮子里面拔高子, dep 也绽放异彩。
15年 Go 官方被社区倒逼,1.5 版本同步带上了一个实验性质的功能 vendor 机制。由于默认情况下是关闭态,所以 normal 用户无感知,super 用户才会去体验。1.6 版本的时候该功能才打开为正常使用状态。
那么什么是 vendor 机制呢?简单说就是在你的项目中包含一个 vendor 的文件夹,它代替了通用的 GOPATH 目录,你项目中所有的依赖都会在这里存在。有了 vendor 好处显而易见,你再也不用手工修改 GOPATH 了,每个人运行环境的包也都统一了起来。
但是问题也随之而来,如果你依赖的一个项目也使用 vendor 管理依赖,那不就形成了嵌套依赖了吗?这种情况怎么处理。
当然还有别的各种问题,总之这也不是一个能拿得出手的成熟方案。
后面又出了 glide 等等社区的方案,但是到这里为止官方还是没有出一个能一统天下的方案。
当时 Google 官方的 Go 负责人 Russ 其实是有和 dep 的开发者 Sam 沟通让他修改一些设计以便将 dep 继承到 go 命令中去,但是由于一些观念上的原因 Sam 并没有接受,所以也因此错过一段良缘。
后面官方由于社区的压力在Go 1.11 版本的时候集成了新特性 Go modules。这是首次以官方名义开发的包管理工具。Go 命令直接支持 modules 相关用法。
Go modules
Go modules 是官方推出的依赖管理工具,Go modules 提供了3个重要的功能:
go.mod
文件,它与package.json
或Pipfile
文件的功能类似。- 机器生成的传递依赖项描述文件 :
go.sum
。 - 不再有 GOPATH 限制。模块可以位于任何路径中。
要想使用 Go modules 请升级到 1.11 及其以上版本,1.13 版本已经默认开启 Go modules,如果想体验这个功能建议将 Go 版本升级到 1.13。
另外,这个功能默认并不是开启的,需要手动设置环境变量开开启:
go env -w GO111MODULE=on
go env -w
是Go 1.13 新增的命令,用于写入环境变量。写入的地方是os.UserCOnfigDir
所在的目录。
GO111MUDULE
变量是 Go modules 的开关。 有以下几个参数:
- auto:如果项目包含了 go.mod 文件,则启用 Go modules 功能。在Go 1.13 中是默认值。
- on:始终开启 Go modules。
- off:禁用 Go modules。
使用
首先我们在 GOPATH外新建一个目录,比如 usr/local/mod-demo
,进入目录下,生成 go.mod
文件:
Go mod init mod-demo
打开我们刚生成的 go.mod 文件可以看到:
module mod-demo
go 1.13
go.mod 文件是开启 modules 的必备配置文件。它记录了当前项目引用的包数据信息。go.mod 文件中定义了以下关键词:
- module:用于定义当前项目的模块路径
- go:用于设置Go 版本信息
- require:用于设置一个特定的模块版本
- exclude:用于从使用中排除一个特定的模块版本
- replace:用于将一个模块版本替换为另一个模块版本
接下来添加项目外的依赖:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
d := gin.Default()
d.GET("/index", func(c *gin.Context) {
c.JSON(200, gin.H{"message":"hello world","data":""})
})
d.Run("127.0.0.1:8080")
}
上面我们添加了 gin 的依赖包,然后看 go.mod 的内容:
module mod-demo
go 1.15
require github.com/gin-gonic/gin v1.6.3
可以看到已经添加了依赖。
以下版本格式都是合法的:
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
gopkg.in/vmihailenco/msgpack.v2 v2.9.1
gopkg.in/yaml.v2 <=v2.2.1
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e
latest
Go module 安装 package 的原則是先拉最新的 release tag,若无tag则拉最新的commit,详见 Modules官方介绍。 Go 会自动生成一个 go.sum 文件来记录 dependency tree:
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=
......
......
......
这里会记录当前以来的依赖包所依赖的依赖。
另外,之前我们在 golang.org 上下载不下来的包,直接使用 github 上的镜像,使用 module 之后可以使用 replace 关键字来个替换:
比如你之前引用是这样的:
require (
golang.org/x/text v0.3.2
)
那么使用 replace 之后就是这样的:
require (
golang.org/x/text v0.3.2
)
replace golang.org/x/text v0.3.2 => github.com/golang.org/text v0.3.2
执行命令go list -m all
也可以查看当前所有依赖项。
Go modules 有如下常用命令:
download download modules to local cache (下载依赖的module到本地cache))
edit edit go.mod from tools or scripts (编辑go.mod文件)
graph print module requirement graph (打印模块依赖图))
init initialize new module in current directory (再当前文件夹下初始化一个新的module, 创建go.mod文件))
tidy add missing and remove unused modules (增加丢失的module,去掉未用的module)
vendor make vendored copy of dependencies (将依赖复制到vendor下)
verify verify dependencies have expected content (校验依赖)
why explain why packages or modules are needed (解释为什么需要依赖)
GOPROXY加速
Go 很多包都会被 GFW 墙掉,现在有了Go modules 就可以解决了。
Go modules 支持配置镜像源,从而更快更稳定的完成 go get 操作,大家只需要 export GOPROXY 配置一下即可,推荐的源地址:https://goproxy.io/。