当你在开发一个项目时,通常都会有一些咱们开发人员依赖的工具。开发、测试、构建或部署过程中运行的工具。
例如,你可以将golang.org/x/text/cmd/gotext
结合使用go:generate
来生成需要翻译的消息目录,或者`honnef.co/go/tools/cmd/staticcheck`[1]在提交更改之前对你的代码执行静态分析。
这其中就有几个有趣的问题——尤其是在开发团队环境中。你如何保证每个人的机器上都安装了必要的工具?而且他们使用的工具都是同一个版本呢?
在 Go 1.17 之前,管理它的惯例是tools.go
在项目中创建一个文件,其中包含import
不同工具和//go:build tools
构建约束的语句。如果您还不太熟悉这种用法,请参阅官方Go Wiki[2]中的描述。
但是从 Go 1.17 开始,你就可以采用另一种方法。 与该方法相比,它有利有弊tools.go
,但很值得了解,并且可能非常适合某些项目。
现在允许你可以直接go run
执行特定版本的远程包。从1.17 发布说明[3]可以看出:
go run 现在接受带有版本后缀的参数(例如,go run example.com/cmd@v1.0.0 )。这会导致 go run 以模块感知模式构建和运行包,忽略当前目录或任何父目录中的 go.mod 文件(如果有的话)。
换句话说,当你在模块之外或在模块内部时,您可以使用它go run package@version
来执行远程包,即使该包不在go.mod
文件中。
作为无需安装即可运行可执行包的快速方法,它也很有用。而不是这样:
$ go install honnef.co/go/tools/cmd/staticcheck@v0.3.1 $ staticcheck ./...
你现在可以这样做(记得只有在1.17之后才可使用):
$ go run honnef.co/go/tools/cmd/staticcheck@v0.3.1 ./...
重要提示:当在执行go run package@version
必要的模块时,会将下载并缓存在您机器上的模块缓存中。因此,当在稍后执行相同的go run
命令时,将会使用缓存(而不是再次下载所有内容)并且它会更快地编译执行完成。
使用 go:generate
让我们看一个例子,我们将`golang.org/x/tools/cmd/stringer`[4]工具与某些iota
常量结合使用go:generate
生成String()
方法。
运行以下命令:
$ mkdir tools
$ go mod init example.com/tools
$ touch main.go
然后将以下代码添加到main.go
:
文件:main.go
File: main.go
package main
import "fmt"
//go:generate go run golang.org/x/tools/cmd/stringer@v0.1.10 -type=Level
type Level int
const (
Info Level = iota
Error
Fatal
)
func main() {
fmt.Printf("%s: Hello world!\n", Info)
}
这里重要的是//go:generate
这一行。当你执行go generate
在此文件上时,它将会依次使用go run
来执行v0.1.10
版本的golang.org/x/tools/cmd/stringer
。
让我们试一试:
$ go generate .
go: downloading golang.org/x/tools v0.1.10
go: downloading golang.org/x/sys v0.0.0-20211019181941-9d821ace8654
go: downloading golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
go: downloading golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
这里看到下载了必要的模块,然后go:generate
命令成功完成执行——生成一个新level_string.go
文件和一个应用程序。像这样:
$ ls
go.mod level_string.go main.go
$ go run .
Info: Hello world!
在 Makefile 中使用
还可以使用该go run package@version
模式在您的脚本或 Makefile 中执行。来让我们创建一个 Makefile,其中包含执行特定版本staticcheck
工具的任务。
$ touch Makefile
.PHONY: audit
audit:
go vet ./...
go run honnef.co/go/tools/cmd/staticcheck@v0.3.1 ./...
如果运行make audit
,将会下载必要的模块,并且该staticcheck
工具会成功地完成其检查。
$ make audit
go vet ./...
go run honnef.co/go/tools/cmd/staticcheck@v0.3.1 ./...
go: downloading honnef.co/go/tools v0.3.1
go: downloading golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a
go: downloading golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e
go: downloading github.com/BurntSushi/toml v0.4.1
如果你第二次运行它,你会看到模块缓存被使用了,所以会更快地运行完成。
$ make audit
go vet ./...
go run honnef.co/go/tools/cmd/staticcheck@v0.3.1 ./...
优点和缺点
优点
就积极方面而言,go run package@version
与tools.go
该方法相比有几个很好的优势:
它的设置更简单,需要的代码更少——不需要
tools.go
文件,没有构建约束,也没有别名导入。它避免了用二进制文件实际上不依赖的东西污染你的依赖关系图。
缺点
如果
go run package@version
在整个代码库中的多个位置都有相同的命令并且想要升级到较新的版本,那么您需要手动更新所有命令(或使用sed
或查找并替换)。使用这种tools.go
方法,只需要go.mod
通过运行go get package@newversion
来更新文件。使用这种
tools.go
方法,可以通过运行go mod verify
验证模块缓存中的缓存代码是否有更改。我不太清楚对于go run package@version
是否有类似的检查(如果你知道这样做的方法,可以告知我一下)。从我有限的测试来看,似乎可以编辑机器上模块缓存中的缓存代码,并且运行go run package@version
也会地使用这个编辑过的代码。如果在脱机状态下工作,运行
go run package@version
可能会失败并出现dial tcp: lookup proxy.golang.org: Temporary failure in name resolution
错误,因为它无法访问 Go 模块镜像——即使您的本地模块缓存中已经有一个副本了。$ make audit go vet ./... go run honnef.co/go/tools/cmd/staticcheck@v0.3.1 ./... go: honnef.co/go/tools/cmd/staticcheck@v0.3.1: honnef.co/go/tools/cmd/staticcheck@v0.3.1: Get "https://proxy.golang.org/honnef.co/go/tools/cmd/staticcheck/@v/v0.3.1.info": dial tcp: lookup proxy.golang.org: Temporary failure in name resolution make: *** [Makefile:4: audit] Error 1
其实当使用这些工具时,这并不是一个问题。你可以通过将
GOPROXY
环境变量设置为direct
在脱机状态下来轻松地绕过它。这样做就可以使go run
绕过go模块镜像,并直接在你的机器上使用缓存的模块。$ export GOPROXY=direct $ make audit go vet ./... go run honnef.co/go/tools/cmd/staticcheck@v0.3.1 ./...
本文由小土翻译自 Using go run to manage tool dependencies[5],翻译不当之处,烦请指出。
参考资料
[1]
honnef.co/go/tools/cmd/staticcheck
: https://staticcheck.io/
Go Wiki: https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module
[3]1.17 发布说明: https://go.dev/doc/go1.17
[4]golang.org/x/tools/cmd/stringer
: https://pkg.go.dev/golang.org/x/tools/cmd/stringer
Using go run to manage tool dependencies: https://www.alexedwards.net/blog/using-go-run-to-manage-tool-dependencies
欢迎关注Go招聘公众号,获取Go专题、大厂内推、面经、简历、股文等相关资料可回复和点击导航查阅。