×

Go 模块解惑:到处都是 GO111MODULE ,这到底什么?

Falcon 2020-06-24 views:
自动摘要

正在生成中……

Go
您可能注意到 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/.