Go Modules 是 Golang 官方最近几个版本推出的原生的包管理方式,在此之前,社区也不乏多种包管理方案。在讨论 Go Modules 之前,我们先回顾一下 Golang 的包管理历史的发展。然后讨论一下 Go Modules 的使用以及一些特性,篇幅有限,有些地方不方便展开,后面有时间再深入。行文仓促,不当之处,多多指教。
包管理的历史
Golang 的包管理一直被大众所诟病的一个点,但是我们可以看到现在确实是在往好的方向进行发展。下面是官方的包管理工具的发展历史:
- 在 1.5 版本之前,所有的依赖包都是存放在 GOPATH 下,没有版本控制。这个类似 Google 使用单一仓库来管理代码的方式。这种方式的最大的弊端就是无法实现包的多版本控制,比如项目 A 和项目 B 依赖于不同版本的 package,如果 package 没有做到完全的向前兼容,往往会导致一些问题。
- 1.5 版本推出了 vendor 机制。所谓 vendor 机制,就是每个项目的根目录下可以有一个 vendor 目录,里面存放了该项目的依赖的 package。
go build的时候会先去 vendor 目录查找依赖,如果没有找到会再去 GOPATH 目录下查找。
- 1.11 版本推出 modules 机制,简称 mod,也就是本文要讨论的重点。modules 的原型其实是 vgo,关于 vgo,可以参考文章末尾的参考链接。
除此之外,社区也一直在有几个活跃的包管理工具,使用广泛且具有代表性的主要有下面几个:
- godep
- glide
- govendor
Go包管理
| 命令 | 作用 |
|---|---|
| go mod download | 下载依赖包到本地(默认为 GOPATH/pkg/mod 目录) |
| go mod edit | 编辑 go.mod 文件 |
| go mod graph | 打印模块依赖图 |
| go mod init | 初始化当前文件夹,创建 go.mod 文件 |
| go mod tidy | 增加缺少的包,删除无用的包 |
| go mod vendor | 将依赖复制到 vendor 目录下 |
| go mod verify | 校验依赖 |
| go mod why | 解释为什么需要依赖 |
GOPATH
GOPATH 是 Go语言中使用的一个环境变量,它使用绝对路径提供项目的工作目录。
$ go envGOARCH="amd64"GOBIN=""GOEXE=""GOHOSTARCH="amd64"GOHOSTOS="linux"GOOS="linux"GOPATH="/home/davy/go"GORACE=""GOROOT="/usr/local/go"GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"GCCGO="gccgo"CC="gcc"GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0"CXX="g++"CGO_ENABLED="1"CGO_CFLAGS="-g -O2"CGO_CPPFLAGS=""CGO_CXXFLAGS="-g -O2"CGO_FFLAGS="-g -O2"CGO_LDFLAGS="-g -O2"PKG_CONFIG="pkg-config"
命令行说明如下:
- 第 1 行,执行 go env 指令,将输出当前 Go 开发包的环境变量状态。
- 第 2 行,GOARCH 表示目标处理器架构。
- 第 3 行,GOBIN 表示编译器和链接器的安装位置。
- 第 7 行,GOOS 表示目标操作系统。
- 第 8 行,GOPATH 表示当前工作目录。
- 第 10 行,GOROOT 表示 Go 开发包的安装目录。
在 Go 1.8 版本之前,GOPATH 环境变量默认是空的。从 Go 1.8 版本开始,Go 开发包在安装完成后,将 GOPATH 赋予了一个默认的目录,参见下表。
GOPATH 在不同平台上的安装路径
| 平 台 | GOPATH 默认值 | 举 例 |
|---|---|---|
| Windows 平台 | %USERPROFILE%/go | C:\Users\用户名\go |
| Unix 平台 | $HOME/go | /home/用户名/go |
设置和使用GOPATH
1) 设置当前目录为GOPATH
选择一个目录,在目录中的命令行中执行下面的指令:
mkdir ***cd ***export GOPATH=`pwd`set GOPATH="dir"go env
建立GOPATH中的源码目录
使用下面的指令创建 GOPATH 中的 src 目录,在 src 目录下还有一个 hello 目录,该目录用于保存源码。

目录结构不对 gopath设置不对
main.go:4:2: cannot find package "github.com/aaa/bbb" in any of:D:\program\go\src\github.com\aaa\bbb (from $GOROOT) //GOROOT下的src目录寻找 //标准库E:\gopathtest\src\github.com\aaa\bbb (from $GOPATH) //GOPATH下的src目录寻找 //本地项目路径
GoPath模式管理下必须有src目录!!!!!
**
gopath的src目录下 有很多自己的项目和第三方依赖,,但是每个项目所需要的依赖版本不一样,因此gopath这种管理模式不行(把第三方依赖全部放在一个gopath下,项目之间会有冲突)

但是go get下来的第三方依赖需要我们手动拷贝进vendor目录,因此诞生了很多第三方依赖管理工具
glide配置文件需要哪些标准库,并下载下来拷贝到vendor目录

https://golang.github.io/dep/docs/introduction.html
https://glide.readthedocs.io/en/latest/glide.yaml/
Go Modules包依赖管理工具
Go modules 是 Go 语言中正式官宣的项目依赖解决方案,Go modules(前身为vgo)于 Go1.11 正式发布,在 Go1.14 已经准备好,并且可以用在生产上(ready for production)了,Go 官方也鼓励所有用户从其他依赖项管理工具迁移到 Go modules。
什么是 Go Modules
Go modules 是 Go 语言的依赖解决方案,发布于 Go1.11,成长于 Go1.12,丰富于 Go1.13,正式于 Go1.14 推荐在生产上使用。
Go moudles 目前集成在 Go 的工具链中,只要安装了 Go,自然而然也就可以使用 Go moudles 了,而 Go modules 的出现也解决了在 Go1.11 前的几个常见争议问题:
- Go 语言长久以来的依赖管理问题。
- “淘汰”现有的 GOPATH 的使用模式。
- 统一社区中的其它的依赖管理工具(提供迁移功能)。
设置环境变量GOPROXY
通过go get命令下载各种第三方依赖时,经常需要访问github/google等被墙网站,配置好代理后可以快速稳定下载依赖。
常用的Go Module代理
- goproxy
- 七牛云
- 阿里云
https://mirrors.aliyun.com/goproxy/
设置环境变量 GOPROXY 可以解决中国大陆无法使用 go get 的问题:
# 启用 Go Modules 功能go env -w GO111MODULE=on# 配置 GOPROXY 环境变量go env -w GOPROXY=https://goproxy.io,direct
Bash (Linux or macOS)
# 启用 Go Modules 功能export GO111MODULE=on# 配置 GOPROXY 环境变量export GOPROXY=https://goproxy.io
检查代理是否设置成功
set GO111MODULE=on //启用 Go Modules 功能set GOARCH=amd64set GOBIN=set GOCACHE=C:\Users\Administrator\AppData\Local\go-buildset GOENV=C:\Users\Administrator\AppData\Roaming\go\envset GOEXE=.exeset GOFLAGS=set GOHOSTARCH=amd64set GOHOSTOS=windowsset GOINSECURE=set GOMODCACHE=C:\Users\Administrator\go\pkg\modset GONOPROXY=set GONOSUMDB=set GOOS=windowsset GOPATH=C:\Users\Administrator\goset GOPRIVATE=set GOPROXY=https://goproxy.io,direct //配置 GOPROXY 环境变量set GOROOT=D:\program\goset GOSUMDB=sum.golang.orgset GOTMPDIR=set GOTOOLDIR=D:\program\go\pkg\tool\windows_amd64set GCCGO=gccgoset AR=arset CC=gccset CXX=g++set CGO_ENABLED=1set GOMOD=NULset CGO_CFLAGS=-g -O2set CGO_CPPFLAGS=set CGO_CXXFLAGS=-g -O2set CGO_FFLAGS=-g -O2set CGO_LDFLAGS=-g -O2set PKG_CONFIG=pkg-configset GOGCCFLAGS=-m64 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Users\ADMINI~1\AppData\Local\Temp\go-build201789070=/tmp/go-build -gno-record-gcc-switches

Go Modules基本使用
在初步了解了 Go modules 的前世今生后,我们正式进入到 Go modules 的使用,首先我们将从头开始创建一个 Go modules 的项目(原则上所创建的目录应该不要放在 GOPATH 之中)。
Go mod的命令
在 Go modules 中,我们能够使用如下命令进行操作:
| 命令 | 作用 |
|---|---|
| go mod init | 生成 go.mod 文件 |
| go mod download | 下载 go.mod 文件中指明的所有依赖 |
| go mod tidy | 整理现有的依赖 |
| go mod graph | 查看现有的依赖结构 |
| go mod edit | 编辑 go.mod 文件 |
| go mod vendor | 导出项目所有的依赖到vendor目录 |
| go mod verify | 校验一个模块是否被篡改过 |
| go mod why | 查看为什么需要依赖某模块 |
Go mod的环境变量
在 Go modules 中有以下常用环境变量,我们可以通过 go env 命令来进行查看,如下:
$ go envGO111MODULE="auto"GOPROXY="https://proxy.golang.org,direct"GONOPROXY=""GOSUMDB="sum.golang.org"GONOSUMDB=""GOPRIVATE=""...
GO111MODULE
Go语言提供了 GO111MODULE 这个环境变量来作为 Go modules 的开关,其允许设置以下参数:
- GO111MODULE=off 禁用 go module,编译时会从 GOPATH 和 vendor 文件夹中查找包;
- GO111MODULE=on 启用 go module,编译时会忽略 GOPATH 和 vendor 文件夹,只根据 go.mod下载依赖;
- GO111MODULE=auto(默认值),当项目在 GOPATH/src 目录之外,并且项目根目录有 go.mod 文件时,开启 go module。

常用的go mod命令如下表所示:
GOPROXY
这个环境变量主要是用于设置 Go 模块代理(Go module proxy),其作用是用于使 Go 在后续拉取模块版本时能够脱离传统的 VCS 方式,直接通过镜像站点来快速拉取。
项目动手操作
在完成 Go modules 的开启后,我们需要创建一个示例项目来进行演示,执行如下命令:
在执行 go mod init 命令时,我们指定了模块导入路径为 github.com/eddycjy/module-repo。接下来我们在该项目根目录下创建 main.go 文件,如下:
package mainimport ("fmt""github.com/eddycjy/mquote")func main() {fmt.Println(mquote.GetHello())}
然后在项目根目录执行 go get github.com/eddycjy/mquote 命令,如下:
1. $ go get github.com/eddycjy/mquote2. go: finding github.com/eddycjy/mquote latest3. go: downloading github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f4. go: extracting github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f
查看 go.mod 文件
在初始化项目时,会生成一个 go.mod 文件,是启用了 Go modules 项目所必须的最重要的标识,同时也是 GO111MODULE 值为 auto 时的识别标识,它描述了当前项目(也就是当前模块)的元信息,每一行都以一个动词开头。
在我们刚刚进行了初始化和简单拉取后,我们再次查看 go.mod 文件,基本内容如下:
module github.com/eddycjy/module-repogo 1.13require (github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f)
为了更进一步的讲解,我们模拟引用如下:
module github.com/eddycjy/module-repogo 1.13require (example.com/apple v0.1.2example.com/banana v1.2.3example.com/banana/v2 v2.3.4example.com/pear // indirectexample.com/strawberry // incompatible)exclude example.com/banana v1.2.4replace example.com/apple v0.1.2 => example.com/fried v0.1.0replace example.com/banana => example.com/fish
- module:用于定义当前项目的模块路径。
- go:用于标识当前模块的 Go 语言版本,值为初始化模块时的版本,目前来看还只是个标识作用。
- require:用于设置一个特定的模块版本。
- exclude:用于从使用中排除一个特定的模块版本。
- replace:用于将一个模块版本替换为另外一个模块版本。
另外你会发现 example.com/pear 的后面会有一个 indirect 标识,indirect 标识表示该模块为间接依赖,也就是在当前应用程序中的 import 语句中,并没有发现这个模块的明确引用,有可能是你先手动 go get 拉取下来的,也有可能是你所依赖的模块所依赖的,情况有好几种。
另外你会发现 example.com/pear 的后面会有一个 indirect 标识,indirect 标识表示该模块为间接依赖,也就是在当前应用程序中的 import 语句中,并没有发现这个模块的明确引用,有可能是你先手动 go get 拉取下来的,也有可能是你所依赖的模块所依赖的,情况有好几种。
查看 go.sum 文件
在第一次拉取模块依赖后,会发现多出了一个 go.sum 文件,其详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。
1. github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=2. github.com/eddycjy/mquote v0.0.1/go.mod h1:ZtlkDs7Mriynl7wsDQ4cU23okEtVYqHwl7F1eDh4qPg=3. github.com/eddycjy/mquote/module/tour v0.0.1 h1:cc+pgV0LnR8Fhou0zNHughT7IbSnLvfUZ+X3fvshrv8=4. github.com/eddycjy/mquote/module/tour v0.0.1/go.mod h1:8uL1FOiQJZ4/1hzqQ5mv4Sm7nJcwYu41F3nZmkiWx5I=5. ...
我们可以看到一个模块路径可能有如下两种:
1. github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=2. github.com/eddycjy/mquote v0.0.1/go.mod h1:ZtlkDs7Mriynl7wsDQ4cU23okEtVYqHwl7F1eDh4qPg=
h1 hash 是 Go modules 将目标模块版本的 zip 文件开包后,针对所有包内文件依次进行 hash,然后再把它们的 hash 结果按照固定格式和算法组成总的 hash 值。
而 h1 hash 和 go.mod hash 两者,要不就是同时存在,要不就是只存在 go.mod hash。那什么情况下会不存在 h1 hash 呢,就是当 Go 认为肯定用不到某个模块版本的时候就会省略它的 h1 hash,就会出现不存在 h1 hash,只存在 go.mod hash 的情况。
查看全局缓存
我们刚刚成功的将 github.com/eddycjy/mquote 模块拉取了下来,其拉取的结果缓存在 $GOPATH/pkg/mod和 $GOPATH/pkg/sumdb 目录下,而在mod目录下会以 github.com/foo/bar 的格式进行存放,如下:
mod├── cache├── github.com├── golang.org├── google.golang.org├── gopkg.in...
需要注意的是同一个模块版本的数据只缓存一份,所有其它模块共享使用。如果你希望清理所有已缓存的模块版本数据,可以执行 go clean -modcache 命令。
