Go-包管理三部曲(GOPATH、GOVENDOR、go mod)

一、前言

Go语言经历了许多年的发展,一开始Go对于包的管理并不完善。时至今日,Go语言引入了go mod 才算是彻底解决了Go包管理杂乱的问题。

目前Go语言包管理分为3个流派,分别是使用GOPATH管理、使用GOVENDOR管理、使用go mod管理。

目前大多数github上的go 包项目都已经完成了向go mod 管理包的转换。

注意:三种方式的教程分别独立,没有关联,使用哪种就按照哪种教程执行就好

 

二、安装goimports工具

如果使用 GOPATH 或 GOVENDOR 方式管理包,则先安装goimports工具。如果使用go mod 方式,则先忽略该步骤。

goimports是Go语言的代码格式化检测工具,它会保证团队内所有成员的代码风格是统一的。

比如你某一行多了一个空格,当你点击保存时,go imports 会自动将多的哪个空格去掉,变成符合代码规范的,当然,这需要通过在idea中进行设置。

先通过 go env 命令确认当前会话的环境变量,尤其是 GOPATH、GO111MODULE 两个

# 进入我的GOPATH目录
cd /Users/project/go
# 创建基本目录
mkdir bin pkg src
# 拉取goimports工具
go get -t golang.org/x/tools/cmd/goimports

执行命令后,目录结构是这样的

 

三、通过GOPATH方式管理项目包

先安装goimports工具(参看第二大点)

使用GOPATH管理包是最早的方式,编译时Go会尝试去GOPATH目录下的src以及GOROOT下的src目录找对应的包。

1、关掉GO111MODULE
export GO111MODULE=off

如果要使用GOPATH方式管理项目,则需要先将GO111MODULE关闭,因为如果不关闭,则1.11以后的版本会默认使用 go mod 方式来管理包管理工具。

2、创建项目目录
cd /Users/project/go/src
mkdir test_1
3、引入外部包
# 进入项目目录
cd /Users/project/go/src/test_1
# 拉取外部包
go get -u go.uber.org/zap

拉取后,目录结构是这样的:

可以看到,通过GOPATH方式管理包,包和项目在统一级别目录下,都在 GOPATH/src 文件夹下。

我们写代码就在项目文件夹下,在这里就是test_1文件夹下。

4、实验是否能正常使用外部包
cd /Users/project/go/src/test_1
vi hello.go

package main

import "go.uber.org/zap"

func main() {
    log, _ := zap.NewProduction()
    log.Warn("Warning Test")
}

编译后执行结果为{"level":"warn","ts":1646275075.348703,"caller":"test_1/hello.go:7","msg":"Warning Test"}

说明可以在项目中正常使用外部包

5、使用GOPATH管理包,它的目录结构最终是这样的:

6、注意:

只有包管理方式为go mod 时,GO111MODULE 才可以也必须设置为on,其他两种包管理方式 GOPATH、GOVENDOR, 都应该也必须将 GO111MODULE 设置为off。

如果在GoLand中无法正常运行,请在GoLand中执行 go env 查看环境变量是否和理想的一致。

如果某项不一致,可以通过 export 命令临时修改会话中的设置,或者 go env -w方式永久修改相关设置。

主要需要查看以下几项:GO111MODULE、GOBIN、GOPATH、GOROOT、GOPROXY 几项。

7、总结:

问题很明显,随着项目的越来越大,src下的目录也会越来越多,因为每引入一个外部包,就会在 GOPATH/src 目录下多一个文件夹,使得项目文件越来越杂乱且臃肿。

而且还存在另一个问题,就是一个项目中会有多个小组,由于时间问题,每个小组使用的某个包的版本可能不一样,这个问题如果使用 GOPATH 来管理包,就没有好的解决方案。基于此,Go又推出了第二种包管理方式 GOVENDOR。

再次提醒,在2018年以后,go就推出了go mod方式来管理项目,相当于php 的composer,在这之前go的包管理其实是一只被诟病的,但随着go mod的诞生,go语言的包管理不再是被诟病的点。

但毕竟还是有些项目还在用GOPATH,所以我们也有必要对其使用进行了解。

 

四、通过GOVENDOR来管理包

先安装goimports工具(参看第二大点)

我的 GOPATH 是 /Users/project/go。

使用GOVENDOR来管理包是基于使用GOPATH管理包存在的问题提出的解决方案。

每一个子项目下都有一个vendor目录,自己的项目所用到的包放在自己的vendor目录下。比如项目组a和项目组b都用到了github的zap包,但是因为开发时间不同,使用的zap包版本不一样,这时,就可以将包拉取到自己项目下的vendor目录,以此实现一个大项目下,多个子项目使用不同版本的同一个包。

Go在编译时,会先去当前项目的vendor目录下找对应的包,如果vendor目录下找不到对应的包,才会继续到GOPATH和GOROOT目录下的src目录下找。

1、关掉GO111MODULE
export GO111MODULE=off
2、创建项目目录
# 进入GOPATH/src文件夹
cd /Users/project/go/src
# 创建项目目录
mkdir test_2
3、引入外部包
# 进入项目目录
cd /Users/project/go/src/test_2
# 拉取外部包
go get -u go.uber.org/zap
4、在项目目录下创建vendor目录
mkdir -p /Users/project/go/src/test_2/vendor
5、将src下刚刚拉取的uber包移动到vendor目录下
cd /Users/project/go/src
mv go.uber.org test_2/vendor 
mv golang.org test_2/vendor
6、创建测试go文件,确认是否可以正常使用拉取的包
cd /Users/project/go/src/test_2
vi hello.go

package main

import "go.uber.org/zap"

func main() {
    log, _ := zap.NewProduction()
    log.Warn("Warning Test")
}

执行结束后,输出结果{"level":"warn","ts":1646283603.876498,"caller":"test_2/hello.go:7","msg":"Warning Test"}

说明通过GOVENDOR方式管理go 包成功。

7、通过GOVENDOR方式管理包,最终的目录结构是这样的

毫无疑问,go vendor就非常类似于PHP的composer了,而且看起来也很清爽,但是有一个问题,就是使用GOVENDOR管理包,需要先使用 go get 将包先拉取到 GOPATH/src 目录下,然后再手工移动到项目的vendor目录下,很麻烦。

幸运的是随着发展,Go也有了类似于composer的第三方管理工具,业内比较流行使用的是 glide(已停止运营)、dep、go dep....

但还有另一个问题,虽然说管理包越来越清晰,但是对目录结构的要求也越来越强,就像不断的打补丁打补丁。

当然Go语言在不断进步,在2018年底Go官方终于推出了非常完善好用的包管理方式,go mod。

 

五、使用 go mod 方式管理包

不用先安装goimports(不用看第二大点)

go mod 是三个包管理方式中最晚推出的,也解决了前两个包管理方式存在的弊端。

我们可以很简单的就将通过GOPATH或GOVENDOR管理包的项目转换到go mod方式管理。

1、将 GO111MODULE 设置为on
export GO111MODULE=on

可以通过修改~/.bash_profile 或使用 go env -w 方式将该值永久设置为on

2、创建基本目录及项目目录
# 进入GOPATH
cd /Users/project/go
# 进入GOPATH
cd /Users/project/go
# 创建基本目录
mkdir bin pkg src
# 创建项目目录
mkdir /Users/project/go/src/test_3
# 进入项目目录
cd /Users/project/go/src/test_3
# 项目go mod初始化,这一步会生成一个go.mod文件
go mod init test_3
3、引入外部包
$ cd /Users/project/go/src/test_3
$ go get -u go.uber.org/zap
go get: added go.uber.org/atomic v1.9.0
go get: added go.uber.org/multierr v1.8.0
go get: added go.uber.org/zap v1.21.0

包的位置不再像 GOPATH 或 GOROOT 方式下默认把包放在GOPATH目录下的src文件夹,而是改为放在GOPATH目录下的pkg文件夹。

其好处是,可以同时共存多个版本,比如两个团队一起开发,开始时间节点不同,导致使用的zap包版本不一样,就可以选择通过GOVENDOR方式或go mod方式来管理包,但毫无疑问,go mod更加方便快捷,且目前github上的go 包基本都已经向go mod方向转变,并大多已经完成了转变,这样我们还是选择go mod更恰当。

4、创建测试go文件,确认是否可以正常使用拉取的包
# 进入项目目录
cd /Users/project/go/src/test_3
vi hello.go

package main

import "go.uber.org/zap"

func main() {
    log, _ := zap.NewProduction()
    log.Warn("Warning Test")
}

执行结束后,输出结果{"level":"warn","ts":1646284608.952651,"caller":"test_3/hello.go:7","msg":"Warning Test"}

说明通过 go mod 方式管理go 包成功。

5、安装goimports
# 进入项目目录
cd /Users/project/go/src/test_3
# 拉取goimports
go get -t golang.org/x/tools/cmd/goimports

 

六、总结

GOPATH 方式管理包,是通过在项目根目录执行 go get,包的位置在GOPATH或GOROOT目录下的src目录下;

GOROOT 方式管理包,是通过在项目根目录执行go get,然后手动将包移动到项目的vendor目录下(当然也可以使用第三方包管理工具,比如glide),但最终包的位置就是位于项目根目录下的vendor目录下。

Go mod方式管理包,则是将GO111MODULE置为on后,在项目根目录执行go get,包的位置在GOPATH目下的pkg文件夹下。