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

set GO111MODULE=off

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语言中使用的一个环境变量,它使用绝对路径提供项目的工作目录。

  1. $ go env
  2. GOARCH="amd64"
  3. GOBIN=""
  4. GOEXE=""
  5. GOHOSTARCH="amd64"
  6. GOHOSTOS="linux"
  7. GOOS="linux"
  8. GOPATH="/home/davy/go"
  9. GORACE=""
  10. GOROOT="/usr/local/go"
  11. GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
  12. GCCGO="gccgo"
  13. CC="gcc"
  14. GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0"
  15. CXX="g++"
  16. CGO_ENABLED="1"
  17. CGO_CFLAGS="-g -O2"
  18. CGO_CPPFLAGS=""
  19. CGO_CXXFLAGS="-g -O2"
  20. CGO_FFLAGS="-g -O2"
  21. CGO_LDFLAGS="-g -O2"
  22. 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

选择一个目录,在目录中的命令行中执行下面的指令:

  1. mkdir ***
  2. cd ***
  3. export GOPATH=`pwd`
  4. set GOPATH="dir"
  5. go env

gopath对目录有些要求,必须设置src目录

建立GOPATH中的源码目录

使用下面的指令创建 GOPATH 中的 src 目录,在 src 目录下还有一个 hello 目录,该目录用于保存源码。

image.png

目录结构不对 gopath设置不对

  1. main.go:4:2: cannot find package "github.com/aaa/bbb" in any of:
  2. D:\program\go\src\github.com\aaa\bbb (from $GOROOT) //GOROOT下的src目录寻找 //标准库
  3. E:\gopathtest\src\github.com\aaa\bbb (from $GOPATH) //GOPATH下的src目录寻找 //本地项目路径

GoPath模式管理下必须有src目录!!!!!
**
image.png
gopath的src目录下 有很多自己的项目和第三方依赖,,但是每个项目所需要的依赖版本不一样,因此gopath这种管理模式不行(把第三方依赖全部放在一个gopath下,项目之间会有冲突)

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

image.png
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://goproxy.io

  • 七牛云

https://goproxy.cn

  • 阿里云

https://mirrors.aliyun.com/goproxy/

设置环境变量 GOPROXY 可以解决中国大陆无法使用 go get 的问题:

  1. # 启用 Go Modules 功能
  2. go env -w GO111MODULE=on
  3. # 配置 GOPROXY 环境变量
  4. go env -w GOPROXY=https://goproxy.io,direct

Bash (Linux or macOS)

  1. # 启用 Go Modules 功能
  2. export GO111MODULE=on
  3. # 配置 GOPROXY 环境变量
  4. export GOPROXY=https://goproxy.io

检查代理是否设置成功

  1. set GO111MODULE=on //启用 Go Modules 功能
  2. set GOARCH=amd64
  3. set GOBIN=
  4. set GOCACHE=C:\Users\Administrator\AppData\Local\go-build
  5. set GOENV=C:\Users\Administrator\AppData\Roaming\go\env
  6. set GOEXE=.exe
  7. set GOFLAGS=
  8. set GOHOSTARCH=amd64
  9. set GOHOSTOS=windows
  10. set GOINSECURE=
  11. set GOMODCACHE=C:\Users\Administrator\go\pkg\mod
  12. set GONOPROXY=
  13. set GONOSUMDB=
  14. set GOOS=windows
  15. set GOPATH=C:\Users\Administrator\go
  16. set GOPRIVATE=
  17. set GOPROXY=https://goproxy.io,direct //配置 GOPROXY 环境变量
  18. set GOROOT=D:\program\go
  19. set GOSUMDB=sum.golang.org
  20. set GOTMPDIR=
  21. set GOTOOLDIR=D:\program\go\pkg\tool\windows_amd64
  22. set GCCGO=gccgo
  23. set AR=ar
  24. set CC=gcc
  25. set CXX=g++
  26. set CGO_ENABLED=1
  27. set GOMOD=NUL
  28. set CGO_CFLAGS=-g -O2
  29. set CGO_CPPFLAGS=
  30. set CGO_CXXFLAGS=-g -O2
  31. set CGO_FFLAGS=-g -O2
  32. set CGO_LDFLAGS=-g -O2
  33. set PKG_CONFIG=pkg-config
  34. set GOGCCFLAGS=-m64 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Users\ADMINI~1\AppD
  35. ata\Local\Temp\go-build201789070=/tmp/go-build -gno-record-gcc-switches

Go Modules包依赖管理 - 图6

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 命令来进行查看,如下:

  1. $ go env
  2. GO111MODULE="auto"
  3. GOPROXY="https://proxy.golang.org,direct"
  4. GONOPROXY=""
  5. GOSUMDB="sum.golang.org"
  6. GONOSUMDB=""
  7. GOPRIVATE=""
  8. ...

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。

image.png
常用的go mod命令如下表所示:

GOPROXY

这个环境变量主要是用于设置 Go 模块代理(Go module proxy),其作用是用于使 Go 在后续拉取模块版本时能够脱离传统的 VCS 方式,直接通过镜像站点来快速拉取。

项目动手操作

在完成 Go modules 的开启后,我们需要创建一个示例项目来进行演示,执行如下命令:

在执行 go mod init 命令时,我们指定了模块导入路径为 github.com/eddycjy/module-repo。接下来我们在该项目根目录下创建 main.go 文件,如下:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/eddycjy/mquote"
  5. )
  6. func main() {
  7. fmt.Println(mquote.GetHello())
  8. }

然后在项目根目录执行 go get github.com/eddycjy/mquote 命令,如下:

  1. 1. $ go get github.com/eddycjy/mquote
  2. 2. go: finding github.com/eddycjy/mquote latest
  3. 3. go: downloading github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f
  4. 4. go: extracting github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f

查看 go.mod 文件

在初始化项目时,会生成一个 go.mod 文件,是启用了 Go modules 项目所必须的最重要的标识,同时也是 GO111MODULE 值为 auto 时的识别标识,它描述了当前项目(也就是当前模块)的元信息,每一行都以一个动词开头。
在我们刚刚进行了初始化和简单拉取后,我们再次查看 go.mod 文件,基本内容如下:

  1. module github.com/eddycjy/module-repo
  2. go 1.13
  3. require (
  4. github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f
  5. )

为了更进一步的讲解,我们模拟引用如下:

  1. module github.com/eddycjy/module-repo
  2. go 1.13
  3. require (
  4. example.com/apple v0.1.2
  5. example.com/banana v1.2.3
  6. example.com/banana/v2 v2.3.4
  7. example.com/pear // indirect
  8. example.com/strawberry // incompatible
  9. )
  10. exclude example.com/banana v1.2.4
  11. replace example.com/apple v0.1.2 => example.com/fried v0.1.0
  12. replace 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. 1. github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=
  2. 2. github.com/eddycjy/mquote v0.0.1/go.mod h1:ZtlkDs7Mriynl7wsDQ4cU23okEtVYqHwl7F1eDh4qPg=
  3. 3. github.com/eddycjy/mquote/module/tour v0.0.1 h1:cc+pgV0LnR8Fhou0zNHughT7IbSnLvfUZ+X3fvshrv8=
  4. 4. github.com/eddycjy/mquote/module/tour v0.0.1/go.mod h1:8uL1FOiQJZ4/1hzqQ5mv4Sm7nJcwYu41F3nZmkiWx5I=
  5. 5. ...

我们可以看到一个模块路径可能有如下两种:

  1. 1. github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=
  2. 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 的格式进行存放,如下:

  1. mod
  2. ├── cache
  3. ├── github.com
  4. ├── golang.org
  5. ├── google.golang.org
  6. ├── gopkg.in
  7. ...

需要注意的是同一个模块版本的数据只缓存一份,所有其它模块共享使用。如果你希望清理所有已缓存的模块版本数据,可以执行 go clean -modcache 命令。

Go Modules 下的 go get 行为