使用 go run 来管理工具依赖

共 4471字,需浏览 9分钟

 ·

2022-05-18 14:20

当你在开发一个项目时,通常都会有一些咱们开发人员依赖的工具。开发、测试、构建或部署过程中运行的工具。

例如,你可以将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@versiontools.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/

[2]

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

[5]

Using go run to manage tool dependencies: https://www.alexedwards.net/blog/using-go-run-to-manage-tool-dependencies




往期推荐


我是 polarisxu,北大硕士毕业,曾在 360 等知名互联网公司工作,10多年技术研发与架构经验!2012 年接触 Go 语言并创建了 Go 语言中文网!著有《Go语言编程之旅》、开源图书《Go语言标准库》等。


坚持输出技术(包括 Go、Rust 等技术)、职场心得和创业感悟!欢迎关注「polarisxu」一起成长!也欢迎加我微信好友交流:gopherstudio

浏览 15
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报