为什么 GO111MODULE 无处不在,以及有关 Go 模块的一切(使用 Go 1.20 更新)
你可能已经注意到,这句话GO111MODULE=on
随处可见。很多 readme 文件中都提到了这一点:
GO111MODULE=on go get golang.org/x/tools/gopls@latest
注意:go get
从 Go 1.17 开始,安装二进制文件时已弃用此命令,并且在 Go 1.18 中将无法使用。如果你使用的是 Go 1.16 或更高版本,则应改用:
go install golang.org/x/tools/gopls@latest
在这篇简短的文章中,我将解释为什么GO111MODULE
在 Go 1.11 中引入它,以及处理 Go 模块时需要了解的注意事项和有趣的部分。
目录:
- 从
GOPATH
至GO111MODULE
- 环境
GO111MODULE
变量GO111MODULE
使用 Go 1.11 和 1.12GO111MODULE
使用 Go 1.13GO111MODULE
使用 Go 1.14GO111MODULE
使用 Go 1.15GO111MODULE
使用 Go 1.16GO111MODULE
使用 Go 1.17- 如果您使用 Git 获取模块,则可以更快地下载依赖项
- 安装二进制文件
GO111MODULE=on go get
已被弃用 go run
知道@version
(终于!)GO111MODULE
使用 Go 1.18GO111MODULE
使用 Go 1.19GO111MODULE
使用 Go 1.20- 为什么到处
GO111MODULE
都是?(Go 1.15 及以下版本) - 静默更新的陷阱
go.mod
(Go 1.15 及以下版本) -u
和陷阱@version
- 使用 Go Modules 时的注意事项
从GOPATH
至GO111MODULE
首先,我们来谈谈 GOPATH。Go 在 2009 年首次推出时,并没有自带包管理器。相反,它go get
会根据导入路径获取所有源码,并将它们存储在 中$GOPATH/src
。当时没有版本控制,“master” 分支代表包的稳定版本。
Go 模块(以前称为 vgo - 版本化 Go)于 Go 1.11 中引入。Go 模块不再使用 GOPATH 来存储每个包的单独 git 检出,而是存储带标签的版本,并go.mod
跟踪每个包的版本。
从那时起,“GOPATH 行为”和“Go Modules 行为”之间的交互就成了 Go 最大的问题之一。一个环境变量造成了 95% 的困扰GO111MODULE
:
环境GO111MODULE
变量
GO111MODULE
是一个环境变量,可以在使用时设置,go
用于更改 Go 的包导入方式。第一个痛点是,根据 Go 版本的不同,它的语义会发生变化。
GO111MODULE
可以通过两种不同的方式设置:
- shell 中的环境变量,例如:
export GO111MODULE=on
。 go env
只有使用时才知道的“隐藏”全局配置go env -w GO111MODULE=on
(仅从 Go 1.12 开始可用)。
如果 Go 的行为似乎符合您的预期,建议您检查过去是否使用go env -w GO111MODULE=on
(或off
) 设置了全局配置:
go env GO111MODULE
请注意,环境变量优先于使用存储的值go env -w GO111MODULE
。
要取消设置全局配置,您可以执行以下操作:
go env -u GO111MODULE
GO111MODULE
使用 Go 1.11 和 1.12
GO111MODULE=on
即使项目位于你的 GOPATH 中,也会强制使用 Go 模块。需要此命令go.mod
才能正常工作。GO111MODULE=off
强制 Go 按照 GOPATH 的方式运行,即使在 GOPATH 之外也是如此。-
GO111MODULE=auto
是默认模式。在此模式下,Go 将表现为- 就像
GO111MODULE=on
你在外面一样GOPATH
, - 类似于
GO111MODULE=off
当您在里面时GOPATH
即使go.mod
存在。
- 就像
每当您处于 GOPATH 中并且想要执行需要 Go 模块(例如二进制文件的特定版本)的操作时go get
,您需要执行以下操作:
GO111MODULE=on go get github.com/golang/mock/tree/master/mockgen@v1.3.1
GO111MODULE
使用 Go 1.13
使用 Go 1.13,GO111MODULE
的默认(auto
)更改:
- 其行为类似于在 GOPATH 之外的
GO111MODULE=on
任何地方使用go.mod
或 ,即使没有go.mod
。因此,使用 Go 1.13 时,你可以将所有存储库保留在 GOPATH 中。 - 其行为类似于
GO111MODULE=off
没有 的 GOPATHgo.mod
。
GO111MODULE
使用 Go 1.14
该GO111MODULE
变量的行为与 Go 1.13 相同。
请注意,发生了一些与此无关的行为细微变化GO111MODULE
:
- 会
vendor/
自动拾取。这有可能会破坏 Gomock(问题vendor/
),因为在 1.14 之前,Gomock 并没有被使用。 cd && GO111MODULE=on go get
当您不想弄乱当前项目时,您仍然需要使用它go.mod
(这太烦人了)。
GO111MODULE
使用 Go 1.15
GO111MODULE
Go 1.15没有任何变化。
GO111MODULE
使用 Go 1.16
从 Go 1.16 开始,默认行为是GO111MODULE=on
,这意味着如果您想继续使用旧GOPATH
方式,则必须强制 Go 不使用 Go Modules 功能:
export GO111MODULE=off
Go 1.16 中最好的消息是,我们终于获得了一个专门用于安装 Go 工具的命令,而不必依赖于go get
不断更新你的工具go.mod
。而不是:
# Old way
(cd && go get golang.org/x/tools/gopls@latest)
您现在可以运行:
go install golang.org/x/tools/gopls@latest
需要注意的是,的语义go install
与 略有不同go get
。正如Go
博客中所述:
为了消除关于使用哪个版本的歧义,
go.mod
使用此安装语法时,程序文件中可以出现的指令有一些限制。特别是,至少目前不允许使用 replace 和 exclude 指令。从长远来看,一旦新版本go install program@version
在足够多的用例中运行良好,我们计划go get
停止安装命令二进制文件。有关详细信息,请参阅问题 43684。
例如,您无法(截至 2021 年 4 月)安装gopls的主版本:
% go install golang.org/x/tools/gopls@master
go: downloading golang.org/x/tools v0.1.1-0.20210316190639-9e9211a98eaa
go: downloading golang.org/x/tools/gopls v0.0.0-20210316190639-9e9211a98eaa
go install golang.org/x/tools/gopls@master: golang.org/x/tools/gopls@v0.0.0-20210316190639-9e9211a98eaa
The go.mod file for the module providing named packages contains one or
more replace directives. It must not contain directives that would cause
it to be interpreted differently than if it were the main module.
go.mod文件中的 replace 指令如下所示:
replace golang.org/x/tools => ../
幸运的是,gopls 项目确保replace
在每次发布之前删除该指令,这意味着您可以使用该latest
标签:
go install golang.org/x/tools/gopls@latest
# ^^^^^^
GO111MODULE
使用 Go 1.17
Go 1.17 于 2021 年 8 月 16 日发布。与 1.16 版本一样,GO111MODULE=on
是默认行为,且GO111MODULE=auto
等同于GO111MODULE=on
。如果您仍然想使用GOPATH
,则必须使用 强制 Go 不使用 Go Modules 功能GO111MODULE=off
(请参阅 部分direnv
)。
可能会影响您使用方式的三个重要变化GO111MODULE
如下:
如果您使用 Git 获取模块,则可以更快地下载依赖项
如果您决定更新代码go
中的以下代码go.mod
行以利用新的模块图修剪功能,则go.mod
需要进行更新。首次使用时go build
,您将看到以下错误:
go build ./...
go: updates to go.mod needed; to update it:
go mod tidy
运行后go mod tidy
,你会看到,go.mod
有了新的块,你的改变相当大require
:
diff --git a/go.mod b/go.mod
index 3af719e..26ed354 100644
--- a/go.mod
+++ b/go.mod
@@ -1,12 +1,12 @@
module github.com/maelvls/users-grpc
-go 1.12
+go 1.17
require (
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/golang/mock v1.4.4
- github.com/golang/protobuf v1.4.3
+ github.com/golang/protobuf v1.5.2
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/hashicorp/go-memdb v1.3.0
@@ -37,8 +37,25 @@ require (
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20201116205149-79184cff4dfe // indirect
google.golang.org/grpc v1.33.2
- google.golang.org/protobuf v1.25.0
+ google.golang.org/protobuf v1.27.1
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
)
+
+require (
+ github.com/beorn7/perks v1.0.0 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
+ github.com/hashicorp/golang-lru v0.5.4 // indirect
+ github.com/hashicorp/hcl v1.0.0 // indirect
+ github.com/inconshreveable/mousetrap v1.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect
+ github.com/prometheus/common v0.4.0 // indirect
+ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/subosito/gotenv v1.2.0 // indirect
+ gopkg.in/yaml.v2 v2.3.0 // indirect
+)
新的require
代码块添加了更多// indirect
行。得益于这些新增的代码行,Go 命令(例如 )的go get
下载工作量减少了(这种机制称为惰性模块加载)。以前,go get
您必须下载每个项目才能访问其文件,即使其中一些项目实际上并未被您的代码使用。这对于基于 git 的项目来说是一个大问题,而对于使用 git 的人(即其他所有人)go.mod
来说则问题较少,因为使用该机制时无需获取整个存储库即可获取。GOPROXY
go.mod
GOPROXY
例如,使用该GOPROXY
机制后,对于标签 v1.4.0 的 cert-manager 项目,HTTP GET 调用次数从 252 次减少到 169 次(HTTP 请求减少了 49%)。HTTP 调用次数是使用 mitmproxy 计算的GOCACHE=off
。虽然 HTTP 调用次数有所减少,但我并没有注意到时间有任何显著的减少。
对于依赖 Git 下载仓库的人来说,最大的区别在于 Go 每次读取文件时go.mod
,都需要下载整个 Git 仓库。我们可以使用以下命令强制 Go 克隆 Git 仓库GOPRIVATE=*
:
# With Go 1.16:
$ time GOPATH=$(mktemp -d) GOPRIVATE='*' go get ./...
161.65s user 34.07s system 38% cpu 8:23.58 total
# With Go 1.17:
$ time GOPATH=$(mktemp -d) GOPRIVATE='*' go get ./...
158.09s user 33.39s system 39% cpu 7:59.63 total
这比之前快了 5%(使用 30 Mbit/s DSL 连接时,节省了 24 秒)。我原本期待更好的结果,但具体时间取决于go.mod
你的设备!
安装二进制文件GO111MODULE=on go get
已被弃用
在 Go 1.17 中,使用go get
安装二进制文件现在显示以下警告:
$ GO111MODULE=on go get golang.org/x/tools/gopls@latest
go get: installing executables with 'go get' in module mode is deprecated.
Use 'go install pkg@version' instead.
For more information, see https://golang.org/doc/go-get-install-deprecation
or run 'go help get' or 'go help install'.
要解决此警告,您可以使用Go 1.16 中go install
教过的方法:@version
go install golang.org/x/tools/gopls@latest
请注意,这@version
意味着 Go 模块模式,这意味着GO111MODULE=on
当您不在具有go.mod
.
go run
知道@version
(终于!)
该go run
命令已教会如何处理@version
!例如go install
,它暗示了GO111MODULE=on
。到目前为止,如果您想运行一次二进制文件,则有两种方法:
- 如果您正在从启用的项目运行二进制文件
go.mod
,则可以指定要在其中运行的二进制文件的版本go.mod
,然后运行go run -mod=mod
。
# Before Go 1.17:
go get github.com/golang/mock/mockgen@v1.3.1
go run -mod=mod github.com/golang/mock/mockgen
# With Go 1.17:
go run github.com/golang/mock/mockgen@v1.3.1
如果你用它来生成模拟,这非常有用//go:generate
。例如,在users-grpc项目中,我能够替换我的//go:generate
指令:
// Old way, before 1.17:
//go:generate go run -mod=mod github.com/golang/mock/mockgen -build_flags=-mod=mod -package mocks -destination ./mock_service.go -source=../ user.go
// New way, Go 1.17:
//go:generate go run github.com/golang/mock/mockgen@v1.4.4 -package mocks -destination ./mock_service.go -source=../user.go
GO111MODULE
使用 Go 1.18
如果您仍然需要使用go get
来安装二进制文件,则需要设置GO111MODULE=off
。建议改用 使用go install
。如果没有GO111MODULE=off
,go get
则只会更新您的go.mod
。否则,它将不会执行任何操作。互联网上的所有自述文件都必须更新;如果不更新,人们会因为看不到正在安装的二进制文件而感到困惑。
GO111MODULE
使用 Go 1.19
GO111MODULE
Go 1.19没有任何变化。
GO111MODULE
使用 Go 1.20
GO111MODULE
Go 1.20没有任何变化。
为什么到处GO111MODULE
都是?(Go 1.15 及以下版本)
现在我们知道,GO111MODULE
在 Go 1.15 及更低版本中启用 Go Modules 行为非常有用,答案如下:这是因为它GO111MODULE=on
允许您选择版本。如果没有 Go Modules,go get
则会从 master 分支获取最新的提交。使用 Go Modules,您可以根据 git 标签选择特定版本。
GO111MODULE=on
在 Go 1.16 发布之前,当我想在最新版本和gopls
(Go 语言服务器)的 HEAD 版本之间切换时,我经常使用:
# With Go 1.15.
GO111MODULE=on go get golang.org/x/tools/gopls@latest
GO111MODULE=on go get golang.org/x/tools/gopls@master
GO111MODULE=on go get golang.org/x/tools/gopls@v0.1
GO111MODULE=on go get golang.org/x/tools/gopls@v0.1.8
GO111MODULE="on" go get sigs.k8s.io/kind@v0.7.0
不过,在 Go 1.16 中,这变得容易多了。我可以用以下代码做同样的事情:
# With Go 1.16.
go install golang.org/x/tools/gopls@latest
静默更新的陷阱go.mod
(Go 1.15 及以下版本)
在 Go 1.15 及更低版本中,我们安装二进制文件的唯一选择是使用go get
。你可能在 README 中遇到过这行奇怪的代码:
(cd && GO111MODULE=on go get golang.org/x/tools/gopls@latest)
注意:后缀
@latest
将使用 gopls 的最新 git 标签。需要注意的是,由于这是一个“已修复”版本,-u
因此不需要 ( ,表示“更新”)@v0.1.8
,而更新已修复版本实际上没有任何意义。另外值得注意的是,使用@v0.1
,go get
将获取该标签的最新补丁版本。
那么,为什么我们需要使用这个调用子shell并转到你的HOME目录的特制命令呢?这又是Go语言的另一个缺陷,Go 1.16已经修复了它:在Go 1.15及更低版本中,默认情况下(你无法关闭此功能),如果你所在的文件夹包含go.mod
,go get
它会用你刚刚安装的内容更新它go.mod
。而对于像gopls或kind 这样的开发二进制文件,你肯定不希望它们出现在go.mod
文件中!
因此,对于使用 Go 1.15 及以下版本的任何人来说,解决方法是提供一行代码,确保您不会位于go.mod
-enabled 文件夹中:(cd && go get)
就是这样做的。
幸运的是,在 Go 1.16 中,现在可以清晰地区分go get
添加依赖项go.mod
(例如 npm install)和go install
安装二进制文件(而不会弄乱你的go.mod
)。使用 Go 1.16,上述内容go get
变为:
go install golang.org/x/tools/gopls@latest
-u
和陷阱@version
我已经多次被这个问题困扰:使用时go get @latest
(至少对于二进制文件而言),应避免使用-u
中定义的依赖项go.sum
。否则,它会将所有依赖项更新到其最新的次要版本……而且由于大量项目选择在次要版本之间进行重大更改(例如 v0.2.0 到 v0.3.0),因此使用-u
很有可能造成破坏。
所以如果你看到这个:
# Both -u and @latest!
GO111MODULE=on go get -u golang.org/x/tools/gopls@latest
那么你会立即意识到(1)它使用旧的 preGo .1.16 方式安装 Go 二进制文件,并且(2)你想使用go.sum
go-geting 二进制文件时给出的记录版本!
Rebecca Stambler提醒我们不要-u
与以下版本一起使用:
-u
不应与@latest
标签一起使用,因为它会为您提供不正确的依赖项版本。
但它在这个问题中有点隐藏......我猜它写在 Go 帮助的某个地方(顺便说一句,与相比,这是一个多么可怕的帮助)但这种警告应该更加明显:也许在安装同时包含和的git help
二进制文件时打印警告?@version
-u
使用 Go Modules 时的注意事项
现在,让我们回顾一下在使用 Go Modules 时遇到的一些注意事项。
请记住,go get
还会更新您的go.mod
在 Go 1.16 发布之前,这是 的一个奇怪之处go get
:有时,它的作用是安装二进制文件或下载软件包。而使用 Go 模块时,如果你在一个带有 的仓库中go.mod
,它会默默地将你正在运行的二进制文件添加go get
到你的 中go.mod
!
幸运的是,Go 1.16go install
已经了解了后缀@version
。有了go install foo@version
,你的本地文件go.mod
就不会受到影响!而在 Go 1.18 中,go get
它将不再安装二进制文件,并且只会用于向你的 中添加依赖项go.mod
。
如果你想在未更新的情况下运行go test
or ,你可以使用标志。例如:go build
go.mod
-mod=readonly
go test -mod=readonly ./...
go build -mod=readonly ./...
Go Modules 的依赖源在哪里
使用 Go Modules 时,编译过程中用到的包go build
会存储在 中$GOPATH/pkg/mod
。在 vim 或 VSCode 中检查“导入”时,你可能会看到 GOPATH 版本的包,而不是编译时使用的 pkg/mod 版本。
出现的第二个问题是当您想要破解某个依赖项时,例如出于测试目的。
解决方案 1:使用go mod vendor
+ go build -mod=vendor
。这将强制go
使用 vendor/ 文件,而不是使用$GOPATH/pkg/mod
1。此选项还解决了 vim 和 VSCode 无法打开包中正确版本文件的问题。
解决方案 2:在末尾添加“替换”行go.mod
:
replace github.com/maelvls/beers => ../beers
../beers
我想要检查和破解的依赖项的本地副本在哪里?
GO111MODULE
根据每个文件夹设置direnv
在旧版本的 Go(1.15 及以下)中,当我从基于 GOPATH 的项目(主要使用 Dep)迁移到 Go Modules 时,我发现自己在两个不同的地方遇到了麻烦:GOPATH 内部和外部。所有 Go Modules 都必须放在 GOPATH 之外,这意味着我的项目位于不同的文件夹中。为了解决这个问题,我GO111MODULE
广泛使用了 。我会将所有项目都放在 GOPATH 中,而对于启用了 Go Modules 的项目,我会设置export GO111MODULE=on
。
注意:由于 Go 1.16 中的默认行为现在是
GO111MODULE=on
,因此不再需要此技巧。
这时候就派上用场了。Direnv 是一个用 Go 编写的轻量级命令,它会在你进入目录时direnv
加载文件,并且存在。对于每个启用 Go Module 的项目,我都会有这样的命令:.envrc
.envrc
.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(从 Go 1.13 开始)跳过包代理,直接从 Github 获取我们的私有包。
但是如何构建 Docker 镜像呢?如何go get
从 docker build 中获取我们的私有仓库?
解决方案 1:供应商
使用go mod vendor
,无需将 Github 凭据传递给 Docker 构建上下文。我们只需将所有内容放入其中vendor/
,问题就解决了。在 Dockerfile 中,-mod=vendor
是必需的,但开发人员甚至不必为此操心,-mod=vendor
因为他们可以使用本地 Git 配置访问私有 Github 存储库。
- 优点:在 CI 上构建速度更快(节省约 10 到 30 秒)
- 缺点:PR 因变更而变得臃肿
vendor/
,并且 repo 的大小可能很大
解决方案 2:无供应商
如果vendor/
太大(例如,对于 Kubernetes 控制器来说,vendor/
大约 30MB),我们可以不用 vendoring 来实现。这需要传递某种形式的 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 插图指南》。
- 2020 年 6 月 22 日更新:它说的
use replace
不仅仅是replace
。 - 2021 年 4 月 8 日更新:使用 Go 1.16 更新。
- 2021 年 9 月 20 日更新:使用 Go 1.17 更新。
- 2023 年 2 月 18 日更新:提及Josh Soref
go env GO111MODULE
报告的问题。我还提到了 Go 1.18、Go 1.19 和 Go 1.20。最后,感谢Ed Randall的评论,我将“为什么 GO111MODULE 无处不在”改写为“为什么 GO111MODULE 在 Go 1.15 及以下版本中无处不在”。我还在daodennis的评论后添加了一条注释,并修复了DongHo Jung报告的一个问题。-mod=readonly