使用 go run 來管理工具依賴
例如,你可以將golang.org/x/text/cmd/gotext結(jié)合使用go:generate來生成需要翻譯的消息目錄,或者`honnef.co/go/tools/cmd/staticcheck`[1]在提交更改之前對(duì)你的代碼執(zhí)行靜態(tài)分析。
這其中就有幾個(gè)有趣的問題——尤其是在開發(fā)團(tuán)隊(duì)環(huán)境中。你如何保證每個(gè)人的機(jī)器上都安裝了必要的工具?而且他們使用的工具都是同一個(gè)版本呢?
在 Go 1.17 之前,管理它的慣例是tools.go在項(xiàng)目中創(chuàng)建一個(gè)文件,其中包含import不同工具和//go:build tools構(gòu)建約束的語句。如果您還不太熟悉這種用法,請(qǐng)參閱官方Go Wiki[2]中的描述。
但是從 Go 1.17 開始,你就可以采用另一種方法。 與該方法相比,它有利有弊tools.go,但很值得了解,并且可能非常適合某些項(xiàng)目。
現(xiàn)在允許你可以直接go run執(zhí)行特定版本的遠(yuǎn)程包。從1.17 發(fā)布說明[3]可以看出:
go run 現(xiàn)在接受帶有版本后綴的參數(shù)(例如,go run example.com/[email protected] )。這會(huì)導(dǎo)致 go run 以模塊感知模式構(gòu)建和運(yùn)行包,忽略當(dāng)前目錄或任何父目錄中的 go.mod 文件(如果有的話)。
換句話說,當(dāng)你在模塊之外或在模塊內(nèi)部時(shí),您可以使用它go run package@version來執(zhí)行遠(yuǎn)程包,即使該包不在go.mod文件中。
作為無需安裝即可運(yùn)行可執(zhí)行包的快速方法,它也很有用。而不是這樣:
$?go?install?honnef.co/go/tools/cmd/[email protected]?$?staticcheck?./...?
你現(xiàn)在可以這樣做(記得只有在1.17之后才可使用):
$?go?run?honnef.co/go/tools/cmd/[email protected]?./...?
重要提示:當(dāng)在執(zhí)行go run package@version必要的模塊時(shí),會(huì)將下載并緩存在您機(jī)器上的模塊緩存中。因此,當(dāng)在稍后執(zhí)行相同的go run命令時(shí),將會(huì)使用緩存(而不是再次下載所有內(nèi)容)并且它會(huì)更快地編譯執(zhí)行完成。
使用 go:generate
讓我們看一個(gè)例子,我們將`golang.org/x/tools/cmd/stringer`[4]工具與某些iota常量結(jié)合使用go:generate生成String()方法。
運(yùn)行以下命令:
$?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/[email protected]?-type=Level
type?Level?int
const?(
????Info?Level?=?iota
????Error
????Fatal
)
func?main()?{
????fmt.Printf("%s:?Hello?world!\n",?Info)
}
這里重要的是//go:generate這一行。當(dāng)你執(zhí)行go generate在此文件上時(shí),它將會(huì)依次使用go run來執(zhí)行v0.1.10版本的golang.org/x/tools/cmd/stringer。
讓我們?cè)囈辉嚕?/p>
$?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命令成功完成執(zhí)行——生成一個(gè)新level_string.go文件和一個(gè)應(yīng)用程序。像這樣:
$?ls??
go.mod??level_string.go??main.go?
$?go?run?.?
Info:?Hello?world!?
在 Makefile 中使用
還可以使用該go run package@version模式在您的腳本或 Makefile 中執(zhí)行。來讓我們創(chuàng)建一個(gè) Makefile,其中包含執(zhí)行特定版本staticcheck工具的任務(wù)。
$?touch?Makefile?
.PHONY:?audit
audit:
????go?vet?./...
????go?run?honnef.co/go/tools/cmd/[email protected]?./...
如果運(yùn)行make audit,將會(huì)下載必要的模塊,并且該staticcheck工具會(huì)成功地完成其檢查。
$?make?audit
go?vet?./...
go?run?honnef.co/go/tools/cmd/[email protected]?./...
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
如果你第二次運(yùn)行它,你會(huì)看到模塊緩存被使用了,所以會(huì)更快地運(yùn)行完成。
$?make?audit
go?vet?./...
go?run?honnef.co/go/tools/cmd/[email protected]?./...
優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn)
就積極方面而言,go run package@version與tools.go該方法相比有幾個(gè)很好的優(yōu)勢:
它的設(shè)置更簡單,需要的代碼更少——不需要 tools.go文件,沒有構(gòu)建約束,也沒有別名導(dǎo)入。它避免了用二進(jìn)制文件實(shí)際上不依賴的東西污染你的依賴關(guān)系圖。
缺點(diǎn)
如果
go run package@version在整個(gè)代碼庫中的多個(gè)位置都有相同的命令并且想要升級(jí)到較新的版本,那么您需要手動(dòng)更新所有命令(或使用sed或查找并替換)。使用這種tools.go方法,只需要go.mod通過運(yùn)行go get package@newversion來更新文件。使用這種
tools.go方法,可以通過運(yùn)行go mod verify驗(yàn)證模塊緩存中的緩存代碼是否有更改。我不太清楚對(duì)于go run package@version是否有類似的檢查(如果你知道這樣做的方法,可以告知我一下)。從我有限的測試來看,似乎可以編輯機(jī)器上模塊緩存中的緩存代碼,并且運(yùn)行go run package@version也會(huì)地使用這個(gè)編輯過的代碼。如果在脫機(jī)狀態(tài)下工作,運(yùn)行
go run package@version可能會(huì)失敗并出現(xiàn)dial tcp: lookup proxy.golang.org: Temporary failure in name resolution錯(cuò)誤,因?yàn)樗鼰o法訪問 Go 模塊鏡像——即使您的本地模塊緩存中已經(jīng)有一個(gè)副本了。$?make?audit
go?vet?./...
go?run?honnef.co/go/tools/cmd/[email protected]?./...
go:?honnef.co/go/tools/cmd/[email protected]:?honnef.co/go/tools/cmd/[email protected]:?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其實(shí)當(dāng)使用這些工具時(shí),這并不是一個(gè)問題。你可以通過將
GOPROXY環(huán)境變量設(shè)置為direct在脫機(jī)狀態(tài)下來輕松地繞過它。這樣做就可以使go run繞過go模塊鏡像,并直接在你的機(jī)器上使用緩存的模塊。$?export?GOPROXY=direct
$?make?audit
go?vet?./...
go?run?honnef.co/go/tools/cmd/[email protected]?./...
本文由小土翻譯自 Using go run to manage tool dependencies[5],翻譯不當(dāng)之處,煩請(qǐng)指出。
參考資料
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 發(fā)布說明: 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
我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗(yàn)!2012 年接觸 Go 語言并創(chuàng)建了 Go 語言中文網(wǎng)!著有《Go語言編程之旅》、開源圖書《Go語言標(biāo)準(zhǔn)庫》等。
堅(jiān)持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio
