Go 模块解惑:到处都是 GO111MODULE ,这到底什么?
您可能注意到 GO111MODULE = on
已经遍地开花。许多阅读资料都有这样的内容:
GO111MODULE=on go get -u golang.org/x/tools/gopls@latest
在这篇短文中,我将解释存在 GO111MODULE
的原因,以及在处理Go模块时的注意事项和需要了解的有趣之处。
##从GOPATH
到GO111MODULE
首先,让我们谈谈GOPATH。当Go在2009年首次推出时,它并没有随包管理器一起提供。取而代之的是 go get
,通过使用它们的导入路径来获取所有源并将其存储在 $ GOPATH / src
中。没有版本控制并且『master』分支表示该软件包的稳定版本。
Go 1.11 引入了 Go 模块(以前称为 vgo- 版本为 Go)。 Go Modules不使用 GOPATH 存储每个软件包的单个 git checkout,而是存储带有go.mod
标记版本的标记版本,并跟踪每个软件包的版本。
从那时起,『GOPATH行为』与『Go Modules行为』之间的交互已成为Go的最大难题之一。一个环境变量造成了95%的痛苦:GO111MODULE
。
GO111MODULE
环境变量
GO111MODULE
是一个环境变量,可以在使用go
更改Go导入包的方式时进行设置。第一个痛点是,根据Go版本,其语义会发生变化。
GO111MODULE
与Go 1.11和1.12
即使项目在您的GOPATH中,
GO111MODULE = on
仍将强制使用Go模块。需要go.mod
正常工作。GO111MODULE = off
强制Go表现出GOPATH方式,即使在GOPATH之外。GO111MODULE = auto
是默认模式。在这种模式下,Go会表现- 当您在
GOPATH
外部时, 设置为GO111MODULE = on
, - 当您位于
GOPATH
内部时,即使存在go.mod
,设置为GO111MODULE = off
。
- 当您在
每当您进入GOPATH中,并且您希望执行的操作需要Go模块(例如,Go get
特定版本的二进制文件),您需要执行以下操作:
GO111MODULE=on go get github.com/golang/mock/tree/master/mockgen@v1.3.1
Go 1.13 下的 GO111MODULE
在 Go 1.13 下, GO111MODULE
的默认行为 (auto
) 改变了:
- 当存在
go.mod
文件时或处于 GOPATH 外, 其行为均会等同于于GO111MODULE=on
。这意味着在 Go 1.13 下你可以将所有的代码仓库均存储在 GOPATH 下。 - 当处于 GOPATH 内且没有
go.mod
文件存在时其行为会等同于GO111MODULE=off
。
所以为什么 GO111MODULE
哪里都是?!
我们已经知道了 GO111MODULE
对于选择是否开启 Go Modules 行为非常有用了,那么标题所述问题的原因就是:由于 GO111MODULE=on
允许你选择一个行为。如果不使用 Go Modules, go get
将会从模块代码的 master 分支拉取,而若使用 Go Modules 则你可以利用 Git Tag 手动选择一个特定版本的模块代码。
当我想要在 gopls
(the Go Language Server,Go 语言服务器)的最新发布(latest 标签对应)版本和最近更新(master 分支对应)版本之间切换时我经常使用 GO111MODULE=on
:
GO111MODULE=on go get -u golang.org/x/tools/gopls@latest
GO111MODULE=on go get -u golang.org/x/tools/gopls@master
GO111MODULE=on go get -u golang.org/x/tools/gopls@v0.1
GO111MODULE=on go get golang.org/x/tools/gopls@v0.1.8
@latest
后缀将会使用 gopls 的 latest Git 标签。值得注意的是,-u
参数(意思是 ‘update’)对于 @v0.1.8
不再需要(因为这已经是一个固定的版本了,而更新一个固定的版本通常并没有意义)。还有一件事, 如果提供类似于 @v0.1
的后缀,go get
将会找寻这一版本号所对应的最后一个修订版本号的 tag。
使用 Go Modules 的说明
现在,让我们看看在我使用 Go Modules 时遇到的一些问题。
记住 go get
同样会更新你的 go.mod
这是 go get
的一个奇怪的行为:通常它是用于提供一个安装或下载包的功能。但是,如果使用了 Go modules,当你在一个有着 go.mod
文件存在的仓库下使用这个命令会将你所下载或安装的包静默记录于 go.mod
文件中。
这是 Go Modules 的一个优点!😁
Go Modules 依赖项的来源在哪里
使用 Go Modules 时,在 go build
期间使用的包存储在 $GOPATH/pkg/mod
中。在尝试检查 vim 或 VSCode 中的『import』时,你可能最终使用的包是 GOPATH 中的版本,而不是编译期间使用的 pkg/mod。
出现的第二个问题是当您想要破解一个依赖项时,例如为了测试的目的。
解决方法 1: 使用 go mod vendor
+ go build -mod=vendor
。这将强制 go
使用 vendor/files 而不是 $GOPATH/pkg/mod
中的一个。该选项还解决了 vim 和 VSCode 不能打开包文件的正确版本的问题。
解决方法 2: 在 go.mod
末尾添加『replace』行:
use replace github.com/maelvls/beers => ../beers
../beers
是我想检查和修改的依赖项的本地副本。
使用 direnv
在每个文件夹的基础上设置 GO111MODULE
从基于 GOPATH 的项目(主要使用 Dep)迁移到 Go Modules 期间,我发现自己在两个不同的地方苦苦挣扎:内部和外部 GOPATH。所有 Go Modules 都必须保留在 GOPATH 之外,这意味着我的项目需要位于不同的文件夹中。
为了解决这个问题,我广泛使用了 GO111MODULE
。我会将所有项目保留在GOPATH 中,对于启用了 Go Modules 的项目,我将设置为 export GO111MODULE = on
。
这是 direnv
派上用场的地方。Direnv 是一个用 Go 编写的轻量级命令,当您进入拥有 .envrc
的目录都会加载文件 .envrc
。对于每一个支持 Go Modules 的项目,我都会有一个 .envrc
:
# .envrc
export GO111MODULE=on
export GOPRIVATE=github.com/mycompany/.
export GOFLAGS=-mod=vendor
GOPRIVATE 禁用了某些导入路径的 Go 代理(Go 1.13)。我还发现设置 -mod=vendor
很有用,这样每个命令都使用 vendor
文件夹(go mod vendor
)。
私有Go模块和Dockerfile
在我的公司中,我们使用很多私有存储库。如上所述,我们可以使用GOPRIVATE
来告诉Go 1.13跳过软件包代理并直接从Github获取我们的私有软件包。
但是如何构建Docker映像呢?如何获取
从Docker构建中获取我们的私有存储库?
####解决方案1: vendoring(供应商)
使用go mod vendor
,无需将Github凭据传递到Docker构建上下文。我们可以将所有内容放到vendoring/
中,问题就解决了。在Dockerfile中,将需要-mod = vendor
,但是开发人员甚至不必为-mod = vendor
操心,因为他们始终可以使用其访问私有Github存储库本地Git配置
- 优点:在CI上更快构建(减少约10至30秒)
- 缺点:PR充斥着
供应商/
变更,并且回购的规模可能很大
解决方案2:no vendoring(无供应商)
如果vendor /
太大(例如,对于Kubernetes控制器,vendor /
大约30MB),我们可以很好地做到这一点而无需供应商。这将需要传递某种形式的GITHUB_TOKEN作为docker build
的参数,并在Dockerfile中设置类似以下内容:
git config --global url."https://foo:${GITHUB_TOKEN}@github.com/company".insteadOf "https://github.com/company"
export GOPRIVATE=github.com/company/.
插图来自Bailey Beougher,摘自《 Kubernetes插图儿童指南》
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: