浅谈GoPath和Go Modules包管理
1、概述
大多数语言都有“依赖”、“包”等概念,Go
语言的依赖处理经历了几次变革
最早的时候,Go
所依赖的所有的第三方库都放在GOPATH
这个目录下面
从v1.5
开始开始引入vendor
模式,如果项目目录下有vendor
目录,那么go
工具链会优先使用vendor
内的包进行编译、测试等
从v1.11
开始,引入了Go Module
作为依赖解决方案,到v1.14
宣布Go Module
已经可以用于生产环境,到v1.16
版本开始Go Module
默认开启
2、GOPATH介绍
2.1 GOPATH目录
GOPATH
是什么,输入如下命令查看
# go env GOPATH
GOPATH="/Users/ssgeek/go"
进入到该目录下,目录结构如下
# cd `go env GOPATH`
# tree -L 2 .
.
├── bin
│ ├── dlv
│ ├── go-outline
│ ├── gomodifytags
│ ├── gopkgs
│ ├── goplay
│ ├── gopls
│ ├── gotests
│ ├── impl
│ └── staticcheck
├── pkg
│ ├── mod
│ └── sumdb
└── src
└── github.com
...
三个目录中存放的文件说明如下
bin //用来存放编译后的可执行文件
pkg //用于存放编译后生成的归档文件
src //用来存放go源码文件
2.2 GOPATH的缺点
在使用GOPATH
的模式下,我们需要将应用代码存放在固定的$GOPATH/src
目录下,并且如果执行go get
来拉取外部依赖会自动下载并安装到$GOPATH
目录下
第三方套件只要不是官方库,都需要放置在GOPATH/src
的路径下才可以使用
go get
最常用在当我们想用别人公开在GitHub
上的套件,可以帮我们从网络clone
到GOPATH/src
里面。虽然这样很方便,但是会发现GOPATH/src
下会很复杂,除了有你自己开发的代码目录,同时也包含其他第三方库的专属目录
我们给不同的项目设置不同的GoPath
,优点非常明显:
便于管理项目,每个项目都是不同的GoPath
,这对于我们管理多个Golang
项目而言,能够非常清晰的处理项目结构。如果我们把所有项目都放在同一个GoPath
的src
包下,那么项目的结构就会变得非常混乱,难以管理
但是当我们需要依赖第三方的包的时候,不同的项目设置不同的GoPath
的缺点也非常明显:
- 第三方依赖的包和我们自己的
Golang
包混在一起,会给我们的项目文件管理带来一定的麻烦 - 不同的
GoPath
都需要下载依赖,那么磁盘中重复的依赖就会非常多,会占用我们大量的磁盘空间
3、GO Module介绍
为了解决GOPATH
的问题,因此官方在1.11
开始推出了Go Modules
的功能。Go Modules
解决方式很像是Java
看到Maven
的做法,将第三方库储存在本地的空间,并且给项目代码去引用
3.1 设定GO111MODULE环境变量
总共可以设置三种不同的值
- auto
默认值,go
命令会根据当前目录来决定是否启用modules
功能。需要满足两种情形:
该目录不在GOPATH/src/
下
当前或上一层目录存在go.mod
文件
- on
go
命令会使用modules
,而不会去GOPATH
目录下查找。
- off
go
命令将不会支持module
功能,寻找依赖按照以前GOPATH
的做法去寻找
目前1.16
版本默认将这个参数设置成on
,而且可能之后的版本会弃用掉GO111MODULE
,因此建议要开发Go
项目时就不再使用GOPATH
了,而是采用Go Modules
的做法,因此建议都设定为on
采用Go Modules
,下载下来的第三方依赖就位于GOPATH/pkg/mod
目录下
3.2 初始化mod
go mod init <module name>
<module name>
可填可不填,不填的话预设就是默认的文件名称go.mod
在此文件中可以写以下几个关键字:
- module
定义模组路径
- go
定义go
语言version
- require
指定依赖,预设是最新版,可以指定版本号
- exclude
排除该依赖和其版本
- replace
使用不同的依赖版本并替换原有的依赖版本注解
indirect
代表被间接导入的依赖包
假设现在我要引入GitHub上的gin-gonic/gin
的依赖,如下定义:
module myProject
go 1.16
require github.com/gin-gonic/gin v1.6.3
再执行以下指令:
go mod xxx
会将需要的依赖安装在GOPATH/pkg/mod
目录里面
gin@v1.4.0 gin@v1.6.3 gin@v1.7.1
除了go.mod
之外,go
命令还维护一个名为go.sum
的文件,其中包含特定模块版本内容的预期加密哈希
go
命令使用go.sum
文件确保这些模块的未来下载检索与第一次下载相同,以确保项目所依赖的模块不会出现意外更改,无论是出于恶意、意外还是其他原因。 go.mod
和go.sum
都应检入版本控制。
go.sum
不需要手工维护,所以可以不用太关注
只要有开启go modules
功能,go get
就不会像以前一样在GOPATH/src
下放置依赖,而是会放在GOPATH/pkg/mod
里面,并且go.mod
会写好引入
3.3 go mod命令
常用
go mod init:初始化go mod, 生成go.mod文件,后可接参数指定 module 名,上面已经演示过。
go mod download:手动触发下载依赖包到本地cache(默认为$GOPATH/pkg/mod目录)
go list -m -json all:以 json 的方式打印依赖详情
其他
go mod graph: 打印项目的模块依赖结构
go mod tidy :添加缺少的包,且删除无用的包
go mod verify :校验模块是否被篡改过
go mod why: 查看为什么需要依赖
go mod vendor :导出项目所有依赖到vendor下
go mod edit :编辑go.mod文件
4、总结
GoPath
所引出的问题,就是因为第三方类库的包所导致的,所以在有了GoModule
之后,GoPath
和GoModule
就分别负责不同的职责,共同为Golang
项目服务
GoPath
用来存放我们从网上拉取的第三方依赖包
GoModule
用来存放我们自己的Golang
项目文件,当自己的项目需要依赖第三方的包的时候,我们通过GoModule
目录下的一个go.mod
文件来引用GoPath
目录pkg
包下的mod
文件夹下的第三方依赖即可
这样以来,既解决了原来只能局限在GoPath
目录src
包下进行编程的问题,也解决了第三方依赖包难以管理和重复依赖占用磁盘空间的问题