为什么 GO111MODULE 无处不在,以及有关 Go 模块的一切(使用 Go 1.20 更新)

2025-06-09

为什么 GO111MODULE 无处不在,以及有关 Go 模块的一切(使用 Go 1.20 更新)

你可能已经注意到,这句话GO111MODULE=on随处可见。很多 readme 文件中都提到了这一点:

GO111MODULE=on go get golang.org/x/tools/gopls@latest
Enter fullscreen mode Exit fullscreen mode

注意:go get从 Go 1.17 开始,安装二进制文件时已弃用此命令,并且在 Go 1.18 中将无法使用。如果你使用的是 Go 1.16 或更高版本,则应改用:

go install golang.org/x/tools/gopls@latest
Enter fullscreen mode Exit fullscreen mode

在这篇简短的文章中,我将解释为什么GO111MODULE在 Go 1.11 中引入它,以及处理 Go 模块时需要了解的注意事项和有趣的部分。


目录:


GOPATHGO111MODULE

首先,我们来谈谈 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
Enter fullscreen mode Exit fullscreen mode

请注意,环境变量优先于使用存储的值go env -w GO111MODULE

要取消设置全局配置,您可以执行以下操作:

go env -u GO111MODULE
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

GO111MODULE使用 Go 1.13

使用 Go 1.13,GO111MODULE的默认(auto)更改:

  • 其行为类似于在 GOPATH 之外的GO111MODULE=on任何地方使用go.mod或 ,即使没有go.mod。因此,使用 Go 1.13 时,你可以将所有存储库保留在 GOPATH 中。
  • 其行为类似于GO111MODULE=off没有 的 GOPATH go.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

GO111MODULEGo 1.15没有任何变化。

GO111MODULE使用 Go 1.16

从 Go 1.16 开始,默认行为是GO111MODULE=on,这意味着如果您想继续使用旧GOPATH方式,则必须强制 Go 不使用 Go Modules 功能:

export GO111MODULE=off
Enter fullscreen mode Exit fullscreen mode

Go 1.16 中最好的消息是,我们终于获得了一个专门用于安装 Go 工具的命令,而不必依赖于go get不断更新你的工具go.mod。而不是:

# Old way
(cd && go get golang.org/x/tools/gopls@latest)
Enter fullscreen mode Exit fullscreen mode

您现在可以运行:

go install golang.org/x/tools/gopls@latest
Enter fullscreen mode Exit fullscreen mode

需要注意的是,的语义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.
Enter fullscreen mode Exit fullscreen mode

go.mod文件中的 replace 指令如下所示:

replace golang.org/x/tools => ../
Enter fullscreen mode Exit fullscreen mode

幸运的是,gopls 项目确保replace在每次发布之前删除该指令,这意味着您可以使用该latest标签:

go install golang.org/x/tools/gopls@latest
#                                   ^^^^^^
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

运行后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
+)
Enter fullscreen mode Exit fullscreen mode

新的require代码块添加了更多// indirect行。得益于这些新增的代码行,Go 命令(例如 )的go get下载工作量减少了(这种机制称为惰性模块加载)。以前,go get您必须下载每个项目才能访问其文件,即使其中一些项目实际上并未被您的代码使用。这对于基于 git 的项目来说是一个大问题,而对于使用 git 的人(即其他所有人)go.mod来说则问题较少,因为使用该机制时无需获取整个存储库即可获取GOPROXYgo.modGOPROXY

例如,使用该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
Enter fullscreen mode Exit fullscreen mode

这比之前快了 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'.
Enter fullscreen mode Exit fullscreen mode

要解决此警告,您可以使用Go 1.16 中go install教过的方法:@version

go install golang.org/x/tools/gopls@latest
Enter fullscreen mode Exit fullscreen mode

请注意,这@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
Enter fullscreen mode Exit fullscreen mode

如果你用它来生成模拟,这非常有用//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
Enter fullscreen mode Exit fullscreen mode

GO111MODULE使用 Go 1.18

如果您仍然需要使用go get来安装二进制文件,则需要设置GO111MODULE=off。建议改用 使用go install。如果没有GO111MODULE=offgo get则只会更新您的go.mod。否则,它将不会执行任何操作。互联网上的所有自述文件都必须更新;如果不更新,人们会因为看不到正在安装的二进制文件而感到困惑。

GO111MODULE使用 Go 1.19

GO111MODULEGo 1.19没有任何变化。

GO111MODULE使用 Go 1.20

GO111MODULEGo 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
Enter fullscreen mode Exit fullscreen mode

不过,在 Go 1.16 中,这变得容易多了。我可以用以下代码做同样的事情:

# With Go 1.16.
go install golang.org/x/tools/gopls@latest
Enter fullscreen mode Exit fullscreen mode

静默更新的陷阱go.mod(Go 1.15 及以下版本)

在 Go 1.15 及更低版本中,我们安装二进制文件的唯一选择是使用go get。你可能在 README 中遇到过这行奇怪的代码:

(cd && GO111MODULE=on go get golang.org/x/tools/gopls@latest)
Enter fullscreen mode Exit fullscreen mode

注意:后缀@latest将使用 gopls 的最新 git 标签。需要注意的是,由于这是一个“已修复”版本,-u因此不需要 ( ,表示“更新”) @v0.1.8,而更新已修复版本实际上没有任何意义。另外值得注意的是,使用@v0.1go get将获取该标签的最新补丁版本。

那么,为什么我们需要使用这个调用子shell并转到你的HOME目录的特制命令呢?这又是Go语言的另一个缺陷,Go 1.16已经修复了它:在Go 1.15及更低版本中,默认情况下(你无法关闭此功能),如果你所在的文件夹包含go.modgo get它会用你刚刚安装的内容更新它go.mod。而对于像goplskind 这样的开发二进制文件,你肯定不希望它们出现在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
Enter fullscreen mode Exit fullscreen mode

-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
Enter fullscreen mode Exit fullscreen mode

那么你会立即意识到(1)它使用旧的 preGo .1.16 方式安装 Go 二进制文件,并且(2)你想使用go.sumgo-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 testor ,你可以使用标志。例如:go buildgo.mod-mod=readonly

go test -mod=readonly ./...
go build -mod=readonly ./...
Enter fullscreen mode Exit fullscreen mode

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/mod1。此选项还解决了 vim 和 VSCode 无法打开包中正确版本文件的问题。

解决方案 2:在末尾添加“替换”行go.mod

replace github.com/maelvls/beers => ../beers
Enter fullscreen mode Exit fullscreen mode

../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
Enter fullscreen mode Exit fullscreen mode

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/\*
Enter fullscreen mode Exit fullscreen mode

插图由 Bailey Beougher 绘制,出自《儿童 Kubernetes 插图指南》。

鏂囩珷鏉ユ簮锛�https://dev.to/maelvls/why-is-go111module-everywhere-and-everything-about-go-modules-24k
PREV
像专业人士一样贡献代码。git 分支模型指南(上)
NEXT
您使用什么 GIT GUI 客户端?