Go 语言中 GoPath 模式与 GoModules 模式介绍
GoPath 模式
当你在电脑上安装好 Go 后,在终端执行 go env
命令,在输出的内容中,你会发现一个 GOPATH
的环境变量,它的值是一个目录路径。
从 Go 1.8 版本开始,安装 Go 开发包时会默认为 GOPATH
变量设置一个目录路径,它表示的是 Go 语言的工作目录,这个目录下会有三个子目录,它们分别是:
-
bin:存放编译后生成的二进制可执行文件
-
pkg:存放编译后生成的
.a
文件 -
src:存放项目的源代码,有自己写的代码,还有通过
go get
命令下载的包
注意当你第一次安装 Go 时,你的电脑上不一定存在 GOPATH 目录,你需要自己创建 GOPATH 的目录。
而所谓的『GoPath开发模式』就是指将项目文件和下载的包放到 $GOPATH/src
目录下进行管理的方式。
GoPath 模式的弊端
在 Go 1.11版本之前,开发者是必须要配置 这个GOPATH
环境变量的,这种代码代码管理模式存在比较严重的问题就是没有版本控制。
因为多个项目都会放在src
目录下,而每个项目依赖的一些第三方包也是下载在src
目录下的,当升级某个依赖包时那就是全局升级了,引用这个依赖包的项目都跟着升级包版本了,这样是一件很危险的事,你不知道升级的包在另外一个项目中是否能正常运行的。而且当多人协同开发时,你不知道别人下载的包是不是你所用的那个版本,容易出错且不好排查原因。
vendor 方案
其实针对 GOPATH 这种方式的弊端,官方也给出了解决方案,就是引入 vendor。解决的思路就是,在每个项目下都创建一个 vendor 目录,每个项目所需的依赖都只会下载到自己vendor目录下,项目之间的依赖包就互不影响了。在使用包时,会先从当前项目下的 vendor 目录查找,然后依次向上级目录查找。这种方式依旧是 GOPATH 模式下的,它解决了不同项目不能使用不同版本库的问题,但是也并不完美。如果多个项目使用的第三方库版本是一样的,那么就会造成相同的库存在多个目录下,占用空间而且没办法集中管理,再一个就是当你要分享自己的项目时,除了源码,还要上传所有依赖的包,才能保证别人使用时不会因为版本问题报错。
官方已经不推荐 GOPATH 的开发模式了,新版本中推荐使用 GoModules 模式,一起来看看。
GoModules 模式
GoModules 模式是 Go 语言 1.11 版本正式推出的,在 1.14 版本时,官方就发话 GoModules 的模拟已经成熟,可以用于生产环境了。所以新学习者们,可以直接用上这种模式了!
使用 GoModules 模式主要依赖于官方发布了自己的包管理工具,即 mod,如果做过前端的话,那你一定熟悉 npm,mod 就是类似于 npm 的工具。当你要使用 GoModules 模式时,你需要主动开启它,在终端输入命令 go env
时,你会发现一个 GO111MODULE
变量:
它就是开关,默认是 “auto” 模式,它所有可以设置的值有:
- auto: 自动模式,当项目下存在 go.mod 文件时,就启用 GoModules 模式;
- on: 开启模块支持,编译时会忽略 GOPATH 和 vendor 文件夹,只根据 go.mod下载依赖;
- off: 关闭模块支持,使用 GOPATH 模式。
所以当你要使用 GoModules 模式时,你需要执行开启命令:
go env -w GO111MODULE=on
注意,如果上面这个命令执行后提示无权限修改系统环境变量时,你也可以在通过 vim ~/.bash_profile
的方式,在其中添加 export GO111MODULE=on
的方式修改,别忘了添加后要执行 source ~/.bash_profile
。如果你电脑上终端用的是zsh
,你还需要在 ~/.zshrc
文件中添加 source ~/.bash_profile
这一行。
然后 go env
查看一下是否开启成功:
当你开启 GoModules 模式时,你就无需将你的 go 项目放到 $GOPATH/src
目录下了,其实在新版 Go 的默认情况下,你就可以把项目随意放置了。接下来就看下怎么用 mod。
使用 go mod 初始化项目
go mod 的命令主要有:
go mod download // 下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录)
go mod edit // 编辑go.mod文件
go mod graph // 打印模块依赖图
go mod init // 初始化当前文件夹, 创建go.mod文件
go mod tidy // 增加缺少的module,删除无用的module
go mod vendor // 将依赖复制到vendor下
go mod verify // 校验依赖
go mod why // 解释为什么需要依赖
我们主要使用 go mod init
来初始化项目,比如我们新建了一个项目目录 go-demo
,那么就需要执行以下命令:
// 需要进入到项目里面
cd go-demo
// 执行 init,init 后面的是自定义的当前项目的包名
go mod init goDemo
此时项目下就会多出一个 go.mod
文件,它的内容如下:
module goDemo // 定义的包名
go 1.16 // 当前使用的 go 版本
下载第三方包
我们尝试下载一个第三方包,使用 go get
命令:
go get github.com/jinzhu/now
注意:如果你下载错误或者缓慢,可能是因为没有设置 GOPROXY 代理,可以参考我的这篇记录:传送门
下载完成后,就会发现项目下多出一个 go.sum
文件,它的作用相当于锁定了当前项⽬依赖的所有模块版本,保证今后项目依赖的版本不会被篡改,更多的介绍可以自行搜索相关文档,一般我们不用管这个。
再看看 go.mod 文件中,多了一行 require github.com/jinzhu/now v1.1.2 // indirect
,这表示当前项目引入了这个包,其中注释 // indirect
表示的是间接的依赖,你可以理解为直接依赖的是 jiinzhu
,间接依赖的是 now
。
这里通过 go get
下载的包储是存在 $GOPATH/pkg/mod
目录下,同时会根据引入的路径来保存,比如上面下载的这个包,它的存放位置就是 $GOPATH/pkg/mod/github.com/jinzhu
。注意 GOPATH 模式下,下载的包是存在 $GOPATH/src
目录下的。
下载指定版本的第三方包
在GoModules 模式下,你也可以下载指定版本的包,比如 go get github.com/jinzhu/now@v1.1.0
,此时在 $GOPATH
的包目录下就存在两个版本的包文件:
因为允许多版本包的存在,所以就不会出现只能用一个版本包的尴尬境地,也不用怕一个项目升级包版本而影响到另外一个项目。
无论你是升级版本还是降级版本,你都可以很方便地替换项目中引入的包版本,使用 go mod edit -replace
:
cd go-demo
go mod edit -replace=now@v1.1.2=now@v1.1.0 // 表示当引入 v.1.1.2 时其实找到的是 v.1.1.0
此时 go.mod
文件变成这样:
module goDemo
go 1.16
require github.com/jinzhu/now v1.1.2 // indirect
replace now v1.1.2 => now v1.1.0 // replace 生效
结尾
关于 Go 语言开发的这两种模式,就介绍到这里了,更多细节要在实际开发中慢慢掌握,后续我可能会补充这部分内容,只是作为初学者,我觉得了解到这里就够了。
两种模式的简单总结:
- GOPATH 模式:项目文件夹和包文件都需要都放在
$GOPATH/src
目录下,不方便控制依赖包版本,一次包升级,所有依赖的项目都升级; - GoModules 模式:项目可以放在任意位置,新版本的 go 默认就可以把项目放在任意位置,通过 go mod 来管理依赖包,允许多个包版本存在,每个项目都维护自己的一份包版本,互不影响。