GOROOT:Go 语言安装根目录的路径,也就是 GO 语言的安装路径。
GOPATH:若干工作区目录的路径。是我们自己定义的工作空间。
GOBIN:GO 程序生成的可执行文件(executable file)的路径。
本文会大篇幅的讲解 Go Modules 的使用,但是在那之前,我仍然会简要介绍一下前两个解决方案 GOPATH 和 go vendor 到底是怎么回事?我认为这是有必要的,因为只有了解它的发展历程,才能知道 Go Modules 的到来是有多么的不容易,多么的意义非凡。
一、最古老的 GOPATH
将你的包或者别人的包全部放在 $GOPATH/src
目录下进行管理的方式,我们称之为 GOPATH
模式。在这个模式下,使用 go install
时,生成的可执行文件会放在 $GOPATH/bin
下
如果你安装的是一个库,则会生成 .a
文件到 $GOPATH/pkg
下对应的平台目录中(由 GOOS
和 GOARCH
组合而成),生成.a
为后缀的文件。
GOOS
,表示的是目标操作系统,有 darwin(Mac),linux,windows,android,netbsd,openbsd,solaris,plan9 等;而 GOARCH
,表示目标架构,常见的有 arm,amd64 等;这两个都是 go env 里的变量,你可以通过 go env 变量名 进行查看.
至此,你可能不会觉得上面的方案会产生什么样的问题,直到你开始真正使用 GOPATH 去开发程序,就不得不开始面临各种各样的问题,其中最严重的就是版本管理问题,因为 GOPATH 根本没有版本的概念。
以下几点是你使用 GOPATH 一定会碰到的问题:
- 你无法在你的项目中,使用指定版本的包,因为不同版本的包的导入方法也都一样
- 其他人运行你的开发的程序时,无法保证他下载的包版本是你所期望的版本,当对方使用了其他版本,有可能导致程序无法正常运行
- 在本地,一个包只能保留一个版本,意味着你在本地开发的所有项目,都得用同一个版本的包,这几乎是不可能的。
二、go vendor 模式的过渡
为了解决 GOPATH 方案下不同项目下无法使用多个版本库的问题,Go v1.5 开始支持 vendor 。
以前使用 GOPATH 的时候,所有的项目都共享一个 GOPATH,需要导入依赖的时候,都来这里找,正所谓一山不容二虎,在 GOPATH 模式下只能有一个版本的第三方库。
解决的思路就是,在每个项目下都创建一个 vendor 目录,每个项目所需的依赖都只会下载到自己vendor目录下,项目之间的依赖包互不影响。在编译时,v1.5 的 Go 在你设置了开启 GO15VENDOREXPERIMENT=1
(注:这个变量在 v1.6 版本默认为1,但是在 v1.7 后,已去掉该环境变量,默认开启 vendor
特性,无需你手动设置)后,会提升 vendor 目录的依赖包搜索路径的优先级(相较于 GOPATH)。
其搜索包的优先级顺序,由高到低是这样的
- 当前包下的 vendor 目录
- 向上级目录查找,直到找到 src 下的 vendor 目录
- 在 GOROOT 目录下查找
- 在 GOPATH 下面查找依赖包
虽然这个方案解决了一些问题,但是解决得并不完美。
如果多个项目用到了同一个包的同一个版本,这个包会存在于该机器上的不同目录下,不仅对磁盘空间是一种浪费,而且没法对第三方包进行集中式的管理(分散在各个角落)。
并且如果要分享开源你的项目,你需要将你的所有的依赖包悉数上传,别人使用的时候,除了你的项目源码外,还有所有的依赖包全部下载下来,才能保证别人使用的时候,不会因为版本问题导致项目不能如你预期那样正常运行。
这些看似不是问题的问题,会给我们的开发使用过程变得非常难受,虽然我是初学者,还未使用过 go vendor,但能有很明显的预感,这个方案照样会另我崩溃。
三、go mod 的横空出世
go modules 在 v1.11 版本正式推出,在最新发布的 v1.14 版本中,官方正式发话,称其已经足够成熟,可以应用于生产上。
从 v1.11 开始,go env
多了个环境变量: GO111MODULE
,这里的 111,其实就是 v1.11 的象征标志, go 里好像很喜欢这样的命名方式,比如当初 vendor 出现的时候,也多了个 GO15VENDOREXPERIMENT
环境变量,其中 15,表示的vendor 是在 v1.5 时才诞生的。
GO111MODULE
是一个开关,通过它可以开启或关闭 go mod 模式。
它有三个可选值:off
、on
、auto
,默认值是auto
。
-
GO111MODULE=off
禁用模块支持,编译时会从GOPATH
和vendor
文件夹中查找包。 -
GO111MODULE=on
启用模块支持,编译时会忽略GOPATH
和vendor
文件夹,只根据go.mod
下载依赖。 -
GO111MODULE=auto
,当项目在$GOPATH/src
外且项目根目录有go.mod
文件时,自动开启模块支持。
go mod 出现后, GOPATH(肯定没人使用了) 和 GOVENDOR 将会且正在被逐步淘汰,但是若你的项目仍然要使用那些即将过时的包依赖管理方案,请注意将 GO111MODULE 置为 off。
具体怎么设置呢?可以使用 go env 的命令,如我要开启 go mod ,就使用这条命令
go env -w GO111MODULE="on"
四、goproxy
随着 module 概念引入go语言,每个引入的 module 拥有了 version。随着代码库的不断更新迭代,大家即使是对同一个代码库的引用也可能用了不同的tag 或者 commit hash,基于这个现状,go1.11 的 module 会比以前更频繁的下载源代码。但是基于中国有中国特色的互联网,我们有时候很难get到我们需要的依赖源代码,进而导致项目编译失败,CI失败。于是,我们需要一个proxy。
参考 golang 配置goproxy 几个可选的地址,推荐使用https://goproxy.cn
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct
如果你的操作系统是mac 或则Linux,可以采用下面的方式进行设置。
$ export GO111MODULE=on
$ export GOPROXY=https://goproxy.cn
五、新建一个HELLOWORLD工程
1.go mode init命令
PS E:\go\HelloWorld> go mod init HelloWorld
go: creating new go.mod: module HelloWorld
PS E:\go\HelloWorld>
此时只生成了go.mod文件,没有go.sum文件
module HelloWorld
go 1.15
2.在hello.go中添加测试代码
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
log.Println("hello world")
}
如果没有安装logrus会提示报错,直接运行会先去安装依赖包(安装到GOPATH路径下),同时更新go.mod,新增go.sum
3.go.mod
module HelloWorld
go 1.15
require github.com/sirupsen/logrus v1.7.0 // indirect
在实际应用上,你会看见更复杂的 go.mod 文件,比如下面这样
module github.com/BingmingWong/module-test
go 1.14
require (
example.com/apple v0.1.2
example.com/banana v1.2.3
example.com/banana/v2 v2.3.4
example.com/pear // indirect
example.com/strawberry // incompatible
)
exclude example.com/banana v1.2.4
replace(
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac => github.com/golang/crypto v0.0.0-20180820150726-614d502a4dac
golang.org/x/net v0.0.0-20180821023952-922f4815f713 => github.com/golang/net v0.0.0-20180826012351-8a410e7b638d
golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0
)
主要是多出了两个 flag:
- exclude: 忽略指定版本的依赖包
- replace:由于在国内访问golang.org/x的各个包都需要f.q,你可以在go.mod中使用replace替换成github上对应的库。
如果我们的调用包暂时没打算上传,也可以使用replace替换成本地包
4.go.sum
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
反观 go.sum 文件,就比较复杂了,密密麻麻的。可以看到,内容虽然多,但是也不难理解
每一行都是由 模块路径,模块版本,哈希检验值 组成,其中哈希检验值是用来保证当前缓存的模块不会被篡改。hash 是以h1:开头的字符串,表示生成checksum的算法是第一版的hash算法(sha256)。
值得注意的是,为什么有的包只有一行
<module> <version>/go.mod <hash>
而有的包却有两行呢
<module> <version> <hash>
<module> <version>/go.mod <hash>
那些有两行的包,区别就在于 hash 值不一行,一个是 h1:hash,一个是 go.mod h1:hash
而 h1:hash 和 go.mod h1:hash两者,要不就是同时存在,要不就是只存在 go.mod h1:hash。那什么情况下会不存在 h1:hash 呢,就是当 Go 认为肯定用不到某个模块版本的时候就会省略它的 h1 hash,就会出现不存在 h1 hash,只存在 go.mod h1:hash 的情况。[引用自 3]
go.mod 和 go.sum 是 go modules 版本管理的指导性文件,因此 go.mod 和 go.sum 文件都应该提交到你的 Git 仓库中去,避免其他人使用你写项目时,重新生成的go.mod 和 go.sum 与你开发的基准版本的不一致。
5.如何给项目添加依赖(写进 go.mod)呢?
有两种方法:
- 你只要在项目中有 import,然后 go build 就会 go module 就会自动下载并添加。
- 自己手工使用 go get 下载安装后,会自动写入 go.mod 。
1.自定义包的目录结构
上面是我
d:\go_project\src\[github.com](http://github.com/)\gemark\bingwallpapers
的目录结构下面是config和logger目录的结构和分别go.mod的写法
实际上这两张图已经说明,这是两个module,如果你要按照原来package的概念来理解就容易误会了。
我们看看config.go和logger.go的package name
这两个go文件中,package的名字都是bingwallpapers,不是config也不是logger文件名作为package name。
在使用了GO111MODULE=on后,我们编译go程序的时候,不再是检测$GOPATH(%GOPATH%)了。
甚至我们的程序都可以不在$GOPATH/src目录中,就可以编译。go build/install/run/test 都是去检测当前编译程序目录下的go.mod文件中的内容。
实际上,无论我们团队开发还是个人开发,无论我们的自定义包在本地还是git上我们完全可以将所有的自定义package做成module,我们引入的不在是packages,而是modules。而go module也解决了同一个项目中依赖了不同版本的某个module。
不同版本的我们可以:
import (
rsa1 github.com/rivest/rsa/v1
rsa2 mit.edu/rivest/rsa/v2
)
import中,package 地址前面的 rsa1 和 rsa2 为别名。