命令行

Go 中的命令包含了一整套工具鏈,這些命令涵蓋了文檔,格式化,代碼檢查,編譯,測試,依賴管理等多個方面,可以說是涉及到了 Go 開發的方方面面。
bug 報告漏洞
build 編譯包和依賴
clean 清除對象文件
doc 展示源代碼中的文檔
env 查看Go環境變量信息
fix 修復因go版本變化而導致的API兼容問題
fmt 源代碼格式化
generate 代碼生成
get 添加依賴
install 安裝並編譯包
list 包/模塊列表命令
mod 模塊維護命令
work 工作區維護命令
run 編譯並運行
test 測試
tool 運行指定的go工具
version 展示go的版本信息
vet 掃描並輸出源代碼中可能存在的問題本文只是簡單的敘述與介紹它們的使用,所有內容參考自官方文檔,想要了解更多細節可以前往cmd/go。
help
第一個要認識的是help命令,通過它可以閱讀命令的用法。有兩種用法,如果要獲取簡短的使用信息,可以在指定命令後面加上-h標志,比如
$ go env -h
usage: go env [-json] [-u] [-w] [var ...]
Run 'go help env' for details.go 會簡潔的展示該命令的用法,它也提示了,想要獲得更詳細的信息就需要使用 help 命令
$ go help env
usage: go env [-json] [-u] [-w] [var ...]
Env prints Go environment information.
By default env prints information as a shell script
(on Windows, a batch file). If one or more variable
names is given as arguments, env prints the value of
each named variable on its own line.
The -json flag prints the environment in JSON format
instead of as a shell script.
The -u flag requires one or more arguments and unsets
the default setting for the named environment variables,
if one has been set with 'go env -w'.
The -w flag requires one or more arguments of the
form NAME=VALUE and changes the default settings
of the named environment variables to the given values.
For more about environment variables, see 'go help environment'.善於利用 help 命令,通過它你可以獲取很多有關命令的信息。
doc
$ go doc -h
Usage of [go] doc:
go doc
go doc <pkg>
go doc <sym>[.<methodOrField>]
go doc [<pkg>.]<sym>[.<methodOrField>]
go doc [<pkg>.][<sym>.]<methodOrField>
go doc <pkg> <sym>[.<methodOrField>]
For more information run
go help doc
Flags:
-C dir
change to dir before running command
-all
show all documentation for package
-c symbol matching honors case (paths not affected)
-cmd
show symbols with package docs even if package is a command
-short
one-line representation for each symbol
-src
show source code for symbol
-u show unexported symbols as well as exporteddoc命令會輸出指定包,常量,函數,類型,變量,方法甚至結構體字段的文檔注釋。在不帶任何參數的情況,它會輸出當前包的注釋
$ go doc也可以指定查看某一個包,比如查看runtime包的文檔注釋
$ go doc runtime
package runtime // import "runtime"
Package runtime contains operations that interact with Go's runtime system,
such as functions to control goroutines. It also includes the low-level type
information used by the reflect package; see reflect's documentation for the
programmable interface to the run-time type system.
......或者某一個類型
$ go doc unsafe.Pointer
package unsafe // import "unsafe"
type Pointer *ArbitraryType
Pointer represents a pointer to an arbitrary type. There are four special
operations available for type Pointer that are not available for other
types:
- A pointer value of any type can be converted to a Pointer.
- A Pointer can be converted to a pointer value of any type.
- A uintptr can be converted to a Pointer.
- A Pointer can be converted to a uintptr.
...或者某一個函數
$ go doc runtime.GC
package runtime // import "runtime"
func GC()
GC runs a garbage collection and blocks the caller until the garbage
collection is complete. It may also block the entire program.它有以下常用下標志
-u:查看私有的類型-all:查看指定包的所有文檔-short:只一行簡短描述-src:輸出源代碼-cmd:對於一些屬於 go 命令的包,也輸出它們包內的代碼文檔。
比如查看runtime.inf變量,這是一個不對外暴露的變量
$ go doc -u runtime.inf
package runtime // import "runtime"
var inf = float64frombits(0x7FF0000000000000)利用好doc命令可以幫助你更方便的閱讀文檔。
另一個可以閱讀命令文檔的方式就是去閱讀源代碼,因為有些命令的文檔並不會寫的那麼仔細,反而在源代碼中會有比較詳細的說明。由於這些命令全部都是由 go 編寫的,閱讀起來也相當的方便。這些命令都位於src/cmd包下,每一個子包就是一個單獨的命令,入口位於cmd/go/main.go文件中
func init() {
base.Go.Commands = []*base.Command{
bug.CmdBug,
work.CmdBuild,
clean.CmdClean,
doc.CmdDoc,
envcmd.CmdEnv,
fix.CmdFix,
fmtcmd.CmdFmt,
generate.CmdGenerate,
modget.CmdGet,
work.CmdInstall,
list.CmdList,
modcmd.CmdMod,
workcmd.CmdWork,
run.CmdRun,
test.CmdTest,
tool.CmdTool,
version.CmdVersion,
vet.CmdVet,
help.HelpBuildConstraint,
help.HelpBuildmode,
help.HelpC,
help.HelpCache,
help.HelpEnvironment,
help.HelpFileType,
modload.HelpGoMod,
help.HelpGopath,
get.HelpGopathGet,
modfetch.HelpGoproxy,
help.HelpImportPath,
modload.HelpModules,
modget.HelpModuleGet,
modfetch.HelpModuleAuth,
help.HelpPackages,
modfetch.HelpPrivate,
test.HelpTestflag,
test.HelpTestfunc,
modget.HelpVCS,
}
}在這裡你會找到 go 的所有子命令,以及它們的幫助文檔信息。
bug
$ go help bug
usage: go bug
Bug opens the default browser and starts a new bug report.
The report includes useful system information.該命令沒有任何參數和任何標志,它會用你的默認瀏覽器訪問github.com/golang/go倉庫的 issue 界面,方便你反饋 bug,除此之外沒有任何其它作用。
version
通過version命令可以查看當前 go 的版本信息。
$ go version -h
usage: go version [-m] [-v] [file ...]在不帶任何參數執行的情況下,它會輸出當前 go 語言的版本
$ go version
go version go1.21.0 windows/amd64它還接收文件路徑作為參數,它將輸出該路徑下所有可以被識別的二進制文件編譯時所采用的 go 版本。
$ go version -v ./
buf.exe: go1.20.2
cobra-cli.exe: go1.21.0
dlv.exe: go1.20.2
goctl.exe: go1.20.2
goimports.exe: go1.20.2
golangci-lint.exe: go1.20.2
gopls.exe: go1.19.3
kratos.exe: go1.20.2
main.exe: go1.19.1
protoc-gen-go-grpc.exe: go1.20.2
protoc-gen-go-http.exe: go1.20.2
protoc-gen-go.exe: go1.20.2
protoc-gen-openapi.exe: go1.20.2
swag.exe: go1.21.0
wire.exe: go1.21.0其中-v參數指定version命令去嘗試輸出無法識別的文件的 go 版本,-m參數輸出二進制文件的模塊信息,以及一些編譯參數,下面是一個簡單的例子。
$ go version -v -m wire.exe
wire.exe: go1.21.0
path github.com/google/wire/cmd/wire
mod github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
dep github.com/google/subcommands v1.0.1 h1:/eqq+otEXm5vhfBrbREPCSVQbvofip6kIz+mX5TUH7k=
dep github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
dep golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b h1:NVD8gBK33xpdqCaZVVtd6OFJp+3dxkXuz7+U7KaVN6s=
build -buildmode=exe
build -compiler=gc
build DefaultGODEBUG=panicnil=1
build CGO_ENABLED=1
build CGO_CFLAGS=
build CGO_CPPFLAGS=
build CGO_CXXFLAGS=
build CGO_LDFLAGS=
build GOARCH=amd64
build GOOS=windows
build GOAMD64=v1go自身也是一個二進制文件,其實在不帶任何參數的情況下,go version輸出的就是自身二進制文件的 go 語言版本,因為cmd/go的所有工具鏈都是由 go 語言自身實現的。
env
通過env命令可以查看所有 go 環境變量的信息,修改這些環境變量將會影響 go 工具鏈的行為。
$ go env -h
usage: go env [-json] [-u] [-w] [var ...]
Run 'go help env' for details.不帶任何參數執行該命令,會輸出 go 所有環境變量的值
$ go env
set GO111MODULE=on
set GOARCH=amd64
...將某一個環境變量名作為參數可以只輸出該變量的值
$ go env GO111MODULE
on加上-json可以輸出其json形式
$ go env -json
{
"AR": "ar",
"CC": "gcc",
......
}通過-w標志,並且以var=value形式作為參數,會永久修改某一個變量的值
$ go env -w GO111MODULE=on使用-u標志,可以將某一個變量恢復為默認值
$ go env -u GO111MODULE執行go help environment可以查看每一個環境變量的介紹
$ go help environment
The go command and the tools it invokes consult environment variables
for configuration. If an environment variable is unset or empty, the go
command uses a sensible default setting. To see the effective setting of
the variable <NAME>, run 'go env <NAME>'. To change the default setting,
run 'go env -w <NAME>=<VALUE>'. Defaults changed using 'go env -w'
are recorded in a Go environment configuration file stored in the
per-user configuration directory, as reported by os.UserConfigDir.
The location of the configuration file can be changed by setting
the environment variable GOENV, and 'go env GOENV' prints the
effective location, but 'go env -w' cannot change the default location.
See 'go help env' for details.
General-purpose environment variables:
GO111MODULE
Controls whether the go command runs in module-aware mode or GOPATH mode.
May be "off", "on", or "auto".
See https://golang.org/ref/mod#mod-commands.
GCCGO
The gccgo command to run for 'go build -compiler=gccgo'.
GOARCH
The architecture, or processor, for which to compile code.
Examples are amd64, 386, arm, ppc64.
GOBIN
The directory where 'go install' will install a command.
GOCACHE
The directory where the go command will store cached
information for reuse in future builds.
......下面介紹一些常用的環境變量
GOVERSION
該環境變量的值取決於 go 語言的版本,而版本號來自於$GOROOT/VERSION文件,該文件記錄了當前 go 的版本號和構建時間。
$ cat $GOROOT/VERSION
go1.21.3
time 2023-10-09T17:04:35Zruntime.Version變量值與GOVERSION的值相同,且該環境變量無法被修改。
GOENV
$GOROOT目錄下會有一個默認的名為go.env的配置文件
$ cat $GOROOT/go.env
# This file contains the initial defaults for go command configuration.
# Values set by 'go env -w' and written to the user's go/env file override these.
# The environment overrides everything else.
# Use the Go module mirror and checksum database by default.
# See https://proxy.golang.org for details.
GOPROXY=https://proxy.golang.org,direct
GOSUMDB=sum.golang.org
# Automatically download newer toolchains as directed by go.mod files.
# See https://go.dev/doc/toolchain for details.
GOTOOLCHAIN=auto它的格式就是簡單的key=value這種形式,通過go env -w key=value命令修改的環境變量值將會被寫入配置文件中。不過也可以不使用默認的配置文件,GOENV環境變量可以手動指定env配置文件的地址,並且GOENV環境變量的值只能被操作系統的環境變量所覆蓋,無法被go env -w命令所修改。
GOHOSTARCH
代表著本機的 CPU 架構,只是用來展示,該環境變量的值並不是從配置文件中讀取,也無法被修改。
GOHOSTOS
代表著本機的操作系統,只是用來展示,該環境變量的值並不是從配置文件中讀取,也無法被修改。
GOOS
編譯時,GOOS的值將會決定將源代碼編譯成哪個目標系統的二進制文件,默認值是GOHOSTOS,也就是本機的操作系統,它有以下幾個可選項
linuxdarwinwindowsnetbsdaixandroid
實際支持的操作系統並不止這些,使用命令,go tool dist list,查看所有支持的值
$ go tool dist list | awk -F '/' '{print $1}' | awk '!seen[$0]++'
aix
android
darwin
dragonfly
freebsd
illumos
ios
js
linux
netbsd
openbsd
plan9
solaris
wasip1
windowsGOARCH
編譯時,GOARCH的值將會決定編譯時采用哪個 CPU 架構的指令,默認值是GOHOSTARCH,也就是本機的 CPU 架構,它有以下幾個可選項
amd64386armppc64
實際上支持的架構不止這些,使用go tool dist list 命令,查看所有支持的值
$ go tool dist list | awk -F '/' '{print $2}' | awk '!seen[$0]++'
ppc64
386
amd64
arm
arm64
riscv64
wasm
loong64
mips
mips64
mips64le
mipsle
ppc64le
s390x需要注意的是,GOOS和GOARCh並不能隨意的進行組合,部分操作系統只能支持特定的 CPU 架構。
GOROOT
GOROOT代表 go 語言安裝位置的根目錄, GOROOT的值無法被直接修改,且只能被操作系統的環境變量所覆蓋。
$ ls $GOROOT -1
api
bin
codereview.cfg
CONTRIBUTING.md
doc
go.env
lib
LICENSE
misc
PATENTS
pkg
README.md
SECURITY.md
src
test
VERSION在根目錄下有以下幾個比較重要的文件夾或文件
lib,存放一些依賴,目前而言只有一個包含世界各國時區信息的庫,位於$GOROOT/lib/time,編譯後的二進制文件不會包含這些時區信息。pkg,存放一些工具庫和頭文件,比如go tool命令會在$GOROOT/pkg/tool目錄下尋找 go 工具鏈的二進制文件bin,存放二進制文件,默認情況下只有go和gofmt這兩個可執行文件,$GOROOT/bin應該被添加到系統變量中,否則無法使用 go 命令。src,存放 go 源代碼VERSION,該文件存放著 go 語言的版本信息go.env,該文件是默認的env配置文件
GOPATH
GOPATH默認值是$HOME/go,該環境變量的值指定了在解析import語句時,去哪裡尋找導入的文件。在早期沒有 gomod 的時候,GOPATH是專門用來存放各種第三方庫的,其結構如下
GOPATH=/home/user/go
/home/user/go/
src/
foo/
bar/ (go code in package bar)
x.go
quux/ (go code in package main)
y.go
bin/
quux (installed command)
pkg/
linux_amd64/
foo/
bar.a (installed package object)gomod 誕生之後,GOPATH就只是一個用來存放go get下載的依賴的地方,以及用於存放go install下載並編譯的二進制文件。需要注意的是GOPATH的位置不能與GOROOT相同,否則將不會起任何作用。
$ go env GOBIN
warning: GOPATH set to GOROOT (/home/user/go) has no effect截至目前筆者寫下本文時,go 語言版本已經來到了 go1.21.3,除了非常古老的項目,基本上沒有人會再使用 gopath 來管理依賴了。
GOBIN
GOBIN是用於存放go install下載並編譯的第三方二進制可執行文件,其默認值為$GOPATH/bin。與$GOROOT/bin一樣,該目錄應該被添加到操作系統的環境變量中,否則的話也無法使用GOBIN目錄的下的二進制文件。
GOMODCACHE
GOMODCACHE表示存放go get下載的依賴所存放的位置,默認值為$GOPATH/pkg/mod。其存放格式如下
$GOMODCACHE/domain/username/project@verion在同級目錄下還會有一個名為sumdb的文件夾,用於存放依賴校驗和數據庫的相關信息。
GOCACHE
存放用於編譯的緩存信息,其默認值為$HOME/.cache/go-build,該目錄下會生成一個README文件。
$ cat $(go env GOCACHE)/README
This directory holds cached build artifacts from the Go build system.
Run "go clean -cache" if the directory is getting too large.
Run "go clean -fuzzcache" to delete the fuzz cache.
See golang.org to learn more about Go.每一次build都會產生許多文件,go 會緩存這些文件以便於下一次編譯時復用。
GOTEMPDIR
用於編譯時產生的臨時文件,例如go run要運行的臨時二進制文件。其默認值為操作系統所指定的臨時目錄,在 mac 或 linux 上為/tmp,windows 上為%TEMP%,也可以修改為用戶所指定的位置。
GO111MODULE
該環境變量表示使用何種方式來管理 go 項目的依賴,有以下三個可用的值
off,關閉 gomod,采用 gopath,並且會忽略一切go.mod文件on,采用 gomod,不使用 gopath(默認)。auto,自動感知,如果項目文件包含go.mod就會采用 gomod 來進行管理
TIP
為什麼叫GO111MODULE,不直接叫GOMODULE,因為 gomod 是在 go1.11 版本第一次推出的。
GOPROXY
go 模塊代理,默認值為https://proxy.golang.org,direct,url 采用逗號分隔,direct意思是直接使用 VCS 跳過模塊代理,只有在前者無法訪問時才會執行後者,還有一個可用的選項是off,表示禁止下載任何模塊。除此之外,GOPROXY還可以是文件地址,比如
GOPROXY=file://$(go env GOMODCACHE)/cache/download通過go get -x可以查看依賴下載過程所執行的命令,就可以知曉有沒有走代理。
$ go get -x github.com/spf13/cast
# get https://goproxy.cn/github.com/@v/list
# get https://goproxy.cn/github.com/spf13/cast/@v/list
# get https://goproxy.cn/github.com/spf13/@v/list
# get https://goproxy.cn/github.com/spf13/@v/list: 404 Not Found (0.118s)
# get https://goproxy.cn/github.com/@v/list: 404 Not Found (0.197s)
# get https://goproxy.cn/github.com/spf13/cast/@v/list: 200 OK (0.257s)
# get https://goproxy.cn/github.com/spf13/cast/@v/v1.5.1.info
# get https://goproxy.cn/github.com/spf13/cast/@v/v1.5.1.info: 200 OK (0.013s)
# get https://goproxy.cn/github.com/spf13/cast/@v/v1.5.1.mod
# get https://goproxy.cn/github.com/spf13/cast/@v/v1.5.1.mod: 200 OK (0.015s)
# get https://goproxy.cn/sumdb/sum.golang.org/supported
# get https://goproxy.cn/sumdb/sum.golang.org/supported: 200 OK (0.064s)
# get https://goproxy.cn/sumdb/sum.golang.org/lookup/github.com/spf13/cast@v1.5.1
# get https://goproxy.cn/sumdb/sum.golang.org/lookup/github.com/spf13/cast@v1.5.1: 200 OK (0.014s)
# get https://goproxy.cn/sumdb/sum.golang.org/tile/8/0/x079/736
# get https://goproxy.cn/sumdb/sum.golang.org/tile/8/0/x079/736: 200 OK (0.016s)
# get https://goproxy.cn/sumdb/sum.golang.org/tile/8/0/x068/334
# get https://goproxy.cn/sumdb/sum.golang.org/tile/8/1/266
# get https://goproxy.cn/sumdb/sum.golang.org/tile/8/0/x068/334: 200 OK (0.023s)
# get https://goproxy.cn/sumdb/sum.golang.org/tile/8/1/266: 200 OK (0.028s)
go: downloading github.com/spf13/cast v1.5.1
# get https://goproxy.cn/github.com/spf13/cast/@v/v1.5.1.zip
# get https://goproxy.cn/github.com/spf13/cast/@v/v1.5.1.zip: 200 OK (0.024s)
go: added github.com/spf13/cast v1.5.1使用模塊代理可以有效的提升模塊的下載速度,國內用戶基本上不使用代理的話無法訪問默認的官方代理,目前公開且可信任的第三方模塊代理如下
https://proxy.golang.com.cn,開源同時提供企業版服務https://goproxy.cn,七牛雲提供並開源
當然也有開源的自建模塊代理方案:goproxy
GOSUMDB
GOSUMDB用於設置依賴庫的校驗和檢測數據庫地址,默認是sum.golang.org,當你設置了代理後,go 就會通過代理來訪問校驗數據庫。
GOPRIVATE
GOPRIVATE環境變量用於設置私有的庫,匹配的庫將不會通過 sumdb 進行校驗,也不會走代理,將通過 VCS 直接下載依賴。該支持通配符設置,使用逗號分隔,如下所示,所有後綴為corp.example.com和名為github.com/gohper/myproject的依賴都不會走代理和 sumdb。
GOPRIVATE=*.corp.example.com,github.com/gohper/myproject也可以直接設置某一用戶或組織
GOPRIVATE=github.com/gopher,github.com/myorganizationGONOPROXY
表明哪些依賴不需要走代理,規則與GOPRIVATE一致,並且會覆蓋GOPRIVATE。
GONOSUMDB
表明哪些依賴不需要走校驗數據庫,規則與GOPRIVATE一致,並且會覆蓋GOPRIVATE。
GOINSECURE
表示哪些依賴直接使用 VCS 進行下載,規則與GOPRIVATE一致,並且會被GONOPROXY和GONOSUMDB覆蓋。
GOVCS
設置模塊管理的版本控制系統,默認public:git|hg,private:all。也可以限制指定域名的 VCS,例如
GOVCS=github.com:git,evil.com:off,*:git|hg在上述的限制中,github 只能用 git,evil.com 則不允許使用,使用|來可以表示多個 VCS。如果不做任何限制,可以如下設置
GOVCS=*:all如果不允許任何 VCS 的使用,可以如下設置
GOVCS=*:offGOWORK
設置工作區是否啟用,默認為空即啟用,如果設置為off,則不啟用,會忽略一切go.work文件。
GOTOOLDIR
設置要使用的 go 工具鏈的位置,默認是$GOROOT/pkg/tool,默認的工具鏈也存放在該位置。
GODEBUG
設置調試選項,以鍵值對的形式控制 go 程序的部分執行行為,例如
GODEBUG=http2client=0,http2server=0這些設置是為了方便在版本更新過程中而出現了不兼容變化時,方便 go 回退到以前的舊行為,例如在1.21中不再允許panic(nil)這種情況發生,為此,go 官方專門記錄了GODEBUG History,前往GODEBUG了解更多細節。
CGO_ENABLED
表示是否開啟 cgo,默認為 1,即開啟,設置為 0 則關閉。
上面這些環境變量都是比較常用的,對於一些不那麼常用的不做過多的介紹,比如 CGO,WASM 之類的,感興趣可以自己了解。
build
go 支持的編譯器有兩種,gccgo 和 gc。gcc 是一個老牌 c/c++的編譯器,支持多種語言包括 go,後者 gc 並不是指的是垃圾回收的意思,它指的是 go compiler,go 語言在 go1.5 時完成了自舉,gc 是完全由 go 語言編寫的編譯器,它的源代碼位於cmd/compile包下,由於完全是 go 語言實現,所以對於了解和學習其內部機制也十分的方便。在默認情況下,編譯器采用 gc 進行編譯。順便提一嘴,go 語言調試器也分兩種,gdb 和 dlv,前者是老牌的 c/c++調試器,支持多種語言,包括 go,後者是 go 語言編寫的調試器,對 go 語言的支持更友好,它同樣也是開源的,推薦使用後者。 build命令的作用就是將 go 源文件編譯成可執行的二進制文件,你會體驗到相當迅速的編譯體驗,這也正是 go 語言的特點之一。
$ go build -h
usage: go build [-o output] [build flags] [packages]
Run 'go help build' for details.它接收三個參數,一個是-o標志所指示的文件輸出路徑,一個是用於定義編譯行為的構建標志build flas,最後一個就是要編譯的包,該參數必須放在最後。下面是一個簡單的例子,沒用到構建標志。
# Windows
$ go build -o .\bin\golearn.exe golearn
# macOS / Linux
$ go build -o ./bin/golearn golearn./bin/golearn.exe是表示輸出路徑,golearn表示要編譯的模塊,也可以是一個入口文件,或是一個文件夾。比如下面簡單的例子是以main.go入口文件作為編譯目標。
# Windows
$ go build -o .\bin\golearn.exe main.go
# macOS / Linux
$ go build -o ./bin/golearn main.go在編譯的時候它會忽略掉所有以_test.go結尾的文件,因為些文件按照約定都是測試文件。
除此之外,build命令還支持相當多的構建標志用於控制編譯時的一些行為。
-x:輸出編譯過程中的詳細指令-n:與-x類似,但是區別是它只是輸出這些指令,但實際上並不會執行。-v:輸出編譯的包-p:編譯過程中的並發數-a:強制重新構建,即使已經是最新的了。-compiler:指定使用哪個編譯器,gccgo或者gc,後者是由 go 編寫的編譯器。-race:開啟競態檢測-msan:開啟內存分析-asan:開啟地址分析-cover:開啟代碼覆蓋檢測-buildmode:指定編譯模式,有archive,c-archive,c-shared,default,shared,exe,pie,plugin這幾個選項。-pgo,指定 pgo 文件-trimpath:消除源文件路徑前綴,比如相對路徑/var/lib/go/src/main.go,消除後在運行時通過runtime獲取到的文件名就只有相對於模塊路徑的相對路徑/main.go,開啟此項後,編譯耗時會明顯上升,大概在 20-40%左右,取決於文件數量。-toolexec,在編譯前執行的一些 go 命令,格式為-toolexec 'cmd args'。-gcflags:指定編譯器 gc 的一些 tag-gccgoflags:指定編譯器 gccgo 的一些 tag-ldflags:指定 link 工具的一些 tag
對於一些 ldflags 之類的傳遞參數,可以傳遞"-help"這樣的參數來獲取其可能的值,比如
$ go build -ldflags -help
usage: link [options] main.o
-B note
add an ELF NT_GNU_BUILD_ID note when using ELF
-E entry
set entry symbol name
......上述這些是比較常用的,對於其它不怎麼常用的可以自行了解。
gcflags
通過gcflags可以向編譯器 gc 傳遞一些參數以控制特定的行為,它的使用格式是-gcflags="pattern=args list",ages list就是參數列表,pattern就是作用范圍,有以下幾個可用的值
main,入口文件所在的頂級包路徑all,當前模塊以及當前模式的所有依賴std,標准庫cmd,作用cmd包下的所有源文件- 通配符,比如
.,./...,cmd/...。
該pattern規則適用於所有支持該格式的標志,例如ldflags。通過如下命令查看其參數可用的值
$ go build -gcflags -help
usage: compile [options] file.go...
-% debug non-static initializers
-+ compiling runtime
-B disable bounds checking
-C disable printing of columns in error messages
-D path
set relative path for local imports
-E debug symbol export
-I directory
add directory to import search path
-K debug missing line numbers
-L also show actual source file names in error messages for positions affected by //line directives
-N disable optimizations
-S print assembly listing
-V print version and exit
-W debug parse tree after type checking
......下面介紹幾個常用的參數
-S:輸出代碼的匯編形式-N:關閉編譯優化-m:輸出優化決策-l:關閉函數內聯-c:編譯的並發數-dwarf:生成 DWARF 標志
比如如果要查看代碼的匯編形式,可以使用-S參數,並且還要關閉優化和內聯,這樣才能還原其本來的形式,如下
$ go build -trimpath -gcflags="-N -l -S" main.go
main.main STEXT size=171 args=0x0 locals=0x58 funcid=0x0 align=0x0
0x0000 00000 (./main.go:9) TEXT main.main(SB), ABIInternal, $88-0
0x0000 00000 (./main.go:9) CMPQ SP, 16(R14)
0x0004 00004 (./main.go:9) PCDATA $0, $-2
0x0004 00004 (./main.go:9) JLS 161
0x000a 00010 (./main.go:9) PCDATA $0, $-1
0x000a 00010 (./main.go:9) PUSHQ BP
0x000b 00011 (./main.go:9) MOVQ SP, BP
0x000e 00014 (./main.go:9) SUBQ $80, SP
0x0012 00018 (./main.go:9) FUNCDATA $0, gclocals·J5F+7Qw7O7ve2QcWC7DpeQ==(SB)
0x0012 00018 (./main.go:9) FUNCDATA $1, gclocals·bDfKCdmtOiGIuJz/x+yQyQ==(SB)
0x0012 00018 (./main.go:9) FUNCDATA $2, main.main.stkobj(SB)
0x0012 00018 (./main.go:10) MOVUPS X15, main..autotmp_0+40(SP)
0x0018 00024 (./main.go:10) LEAQ main..autotmp_0+40(SP), CX
0x001d 00029 (./main.go:10) MOVQ CX, main..autotmp_2+32(SP)ldflags
通過 ldflags 可以向鏈接器傳遞一些參數以控制特定的行為,通過如下命令來查看ldflags所有可用的值,接近二三十個。
$ go build -ldflags -help
usage: link [options] main.o
-B note
add an ELF NT_GNU_BUILD_ID note when using ELF
-E entry
set entry symbol name
-H type
set header type
-I linker
use linker as ELF dynamic linker
-L directory
add specified directory to library path
-R quantum
set address rounding quantum (default -1)
-T int
set the start address of text symbols (default -1)
-V print version and exit
-X definition
add string value definition of the form importpath.name=value
-a no-op (deprecated)
.....ldflags的-X參數是一個非常實用的功能,它可以在鏈接時定義指定包的字符串變量的值。通過這個功能,我們可以很方便的在編譯時注入一些元信息。而且它只是一個變量,所以也方便在運行時獲取,下面是一個簡單的例子。
package main
import "fmt"
var (
Version string
)
func main() {
fmt.Println(Version)
}執行命令
go build -ldflags "-X main.Version=$(git describe --always)" main.go運行後就會輸出 git 提交的 sha1 校驗和。
5e3fd7a另外一些比較實用的參數有
-w:不生成 DWARF,這是一種方便調試源碼的信息。-s:禁用符號表
這兩個通常放一塊用,可以顯著的減小編譯後的二進制文件的體積,大概有 40%-50%左右,缺點也很明顯,沒法進行調試,下面是一個例子。
$ go build -ldflags="-w -s" main.go交叉編譯
go 語言編譯總共有兩大特點,第一個就是快,另外一大特點就是交叉編譯,交叉編譯指的是可以在本地編譯成其它系統的目標代碼,例如在windows上編譯成linux或darwin上的二進制文件,反過來也是一樣。交叉編譯支持的語言非常多,這並不是什麼稀奇事,但是 go 語言交叉編譯非常的簡單,只需要以下兩步
- 設置 GOOS 環境變量,選擇你的目標操作系統
- 設置 GOARCH 環境變量,選擇你的目標 CPU 架構
- 像平時一樣使用
go build進行編譯
整個過程非常短,不需要使用額外的工具或配置,而且速度跟平時一樣快。如下所示
build_linux:
SET CGO_ENABLED=0
SET GOOS="linux"
SET GOARCH="amd64"
go build -o golearn main.go
build_mac:
SET CGO_ENABLED=0
SET GOOS="darwin"
SET GOARCH="amd64"
go build -o golearn main.go
build_win:
SET CGO_ENABLED=0
SET GOOS="win"
SET GOARCH="amd64"
go build -o golearn.exe main.go
.PHONY: build_linux \
build_mac \
build_win第一步SET CGO_ENABLED=0這一項禁用了 cgo,一旦你的代碼中使用了 cgo,那麼就無法正常使用交叉編譯。第二步SET GOOS設置目標系統,可選的有linux,darwin,windwos,netbsd。第三步設置 CPU 架構,SET GOARCH,可選的有amd64,386,arm,ppc64。最後一步就是像往常一樣進行編譯。
編譯控制
build命令可以通過tags來達到控制編譯的效果,它以一種指令的方式存在於源代碼中,看個例子,product.go文件
// +build product
package main
import "fmt"
func main() {
fmt.Println("product")
}debug.go文件
// +build debug
package main
import "fmt"
func main() {
fmt.Println("debug")
}它們都有一個// +build指令,表示它們在什麼情況下才會被編譯。其基本格式為
// +build tag1 tag2
package pkg_name有幾個必須遵守的規則
//與+build必須隔一個空格- 它必須位於包聲明的上方
- 與包聲明必須隔一行空行
除此之外,它還可以通過簡單的間隔來達到邏輯控制的目的,空格表示 OR,逗號表示 AND,!表示 NOT。比如下面這個例子
// +build windows linux
package pkg_name表示在 windows 或者 linux 平台下會將當前文件編譯進去。
// +build windows,amd64,!cgo linux,i386,cgo
package pkg_name這個例子表示的是在 windows 平台 amd64 架構且未啟用 cgo 或者是 linux 平台 i386 架構且啟用了 cgo 才會將其編譯。如果你只是單純的不想讓某個文件不參加編譯,可以使用ignore。
// +build ignore
package pkg_name也可以存在多行指令
// +build windows
// +build amd64
package pkg_name多行指令以 AND 方式進行處理。對於平台和架構這些 tag,在編譯時 go 會自動傳入,我們也可以傳入自定義的 tag,就拿最開始的拿兩個文件舉例
$ go build -tags="debug" . && ./golearn.exe
debug
$ go build -tags="product" . && ./golearn.exe
product可以看到傳入不同 tag 時輸出不同,編譯控制的目的也就達到了。
run
run命令與build都會將源代碼進行編譯,不同的是run命令在編譯完成後會直接運行。 run命令為了加快編譯速度,在編譯過程中不會生成調試信息,所以也就不支持調試,並且只是生成一個臨時的二進制文件,通常存放在GOTMEPDIR目錄下,例如/temp/go-build2822241271/b001/exe/main.exe。
$ go run -h
usage: go run [build flags] [-exec xprog] package [arguments...]
Run 'go help run' for details.它也支持build命令的構建標志,還提供了一個參數-exec來指明由哪個程序來運行二進制文件,[arguments...]指的是程序的運行參數。下面是一個例子
package main
import (
"fmt"
"os"
)
var (
Version string
)
func main() {
fmt.Println(Version)
fmt.Println(os.Args[1:])
}使用go run運行
$ go run -ldflags="-X main.Version=$(git describe --always)" main.go hello
5e3fd7a
[hello]總體上使用起來與go build沒有太大的差別,就不再做過多的贅述。
tool
tool命令本身沒有任何功能,它的作用是直接調用cmd/目錄下的工具,例如cmd/compile就是自帶的編譯器。通過go tool可以直接調用這些工具,不用去手動執行這些工具的二進制文件。
$ go tool -h
usage: go tool [-n] command [args...]使用-n參數打印出其所有支持的命令參數
$ go tool -n
addr2line
asm
buildid
cgo
compile
covdata
cover
doc
fix
link
nm
objdump
pack
pprof
test2json
trace
vet這些工具存放在GOROOT/pkg/tool目錄下,並且根據操作系統和 CPU 架構對工具進行分組,如下。
$ ls $GOROOT/pkg/tool/windows_amd64/ -1
addr2line.exe*
asm.exe*
buildid.exe*
cgo.exe*
compile.exe*
covdata.exe*
cover.exe*
doc.exe*
fix.exe*
link.exe*
nm.exe*
objdump.exe*
pack.exe*
pprof.exe*
test2json.exe*
trace.exe*
vet.exe*使用go doc cmd/command格式查看每個命令的用法,比如
$ go doc cmd/compile
Usage:
go tool compile [flags] file...
The specified files must be Go source files and all part of the same package.
The same compiler is used for all target operating systems and architectures.
The GOOS and GOARCH environment variables set the desired target.
Flags:
-D path
Set relative path for local imports.
-I dir1 -I dir2
Search for imported packages in dir1, dir2, etc,
after consulting $GOROOT/pkg/$GOOS_$GOARCH.
-L
Show complete file path in error messages.
...cmd/compile支持的標志參數,也就是前面提到過的gcflags支持的參數。go tool compile與go build的不同在於,前者只是負責編譯,並且只能以文件作為參數,後者可以以文件夾,包,文件作為參數,而且不僅做了編譯源代碼這一件事,還負責鏈接文件,清除無用的文件等,前者是後者的一部分。我們可以打印build過程中執行的命令
$ go build -n main.go
#
# internal/goarch
#
mkdir -p $WORK\b004\
cat >$WORK\b004\importcfg << 'EOF' # internal
# import config
EOF
"/golang/pkg/tool/windows_amd64/compile.exe" -o "$WORK/b004/_pkg_.a" -trimpath "$WORK/b004=>" -p internal/goarch -std -+ -complete -buildid 3gunEkUExGdhOPa2rFsh/3gunEkUExGdhOPa2rFsh -goversion go1.21.0 -c=4 -nolocalimports -importcfg "$WORK/b004/importcfg" -pack "/golang/src/internal/goarch/goarch.go" "/golang/src/internal/goarch/goarch_amd64.go" "/golang/src/internal/goarch/zgoarch_amd64.go"
"/golang/pkg/tool/windows_amd64/buildid.exe" -w "$WORK/b004/_pkg_.a" # internal
...在過程中可以看到有這麼一段/golang/pkg/tool/windows_amd64/compile.exe,正是調用了編譯器。除了compile之外,還有很多工具可以調用,很多 go 命令實際上都是它們的別名。
clean
clean命令用於清除編譯過程中生成的對象文件
$ go clean -h
usage: go clean [clean flags] [build flags] [packages]
Run 'go help clean' for details.它支持以下標志
-i:清除對應的歸檔文件或二進制文件-n:打印將要清除過程要執行的命令但實際並不執行-x:打印清除過程中的要執行的命令並執行-r:通過import path遞歸的進行清除-cache,清除所有go build產生的緩存-testcache:清除所有產生的測試緩存-modcache:清除所有下載的模塊緩存-fuzzcache:清除fuzz test產生的緩存。
當使用go tool compile時,是直接調用編譯器命令,並不像go build那會做很多善後處理,就會產生對象文件。比如執行如下的命令
go tool compile -N -S -l main.go就會生成一個名為main.o的文件,使用go clean命令清除即可。或者使用-n參數打印將要執行的命令。
$ go clean -n
rm -f golearn golearn.exe golearn golearn.exe golearn.test golearn.test.exe golearn.test golearn.test.exe api api.exe main main.exe清除編譯緩存,它會刪除GOCACHE目錄下產生的編譯緩存
$ go clean -cache -n
rm -r /cache/00 /cache/01 /cache/02清除 fuzz test 產生的緩存,這些緩存默認存放在GOCACHE/fuzz/目錄下
$ go clean -fuzzcache -n
rm -rf /cache/fuzzfix
go 語言截至到撰寫本文時已經有十年了,在語言不斷更新和修改的過程中,難免會出現一些因 API 的變化而導致的不兼容,fix命令就是為此而生的,它會檢測源文件中那些已經過時的 API 並將其替換為新的 API。
$ go fix -h
usage: go fix [-fix list] [packages]
Run 'go help fix' for details.它支持文件夾,文件名,目錄作為參數,接收-fix標志來傳遞參數以表明進行何種修改,可以通過got tool fix -help命令查看可用的值
$ go tool fix -help
usage: go tool fix [-diff] [-r fixname,...] [-force fixname,...] [path ...]
-diff
display diffs instead of rewriting files
-force string
force these fixes to run even if the code looks updated
-go string
go language version for files
-r string
restrict the rewrites to this comma-separated list
Available rewrites are:
buildtag
Remove +build comments from modules using Go 1.18 or later
cftype
Fixes initializers and casts of C.*Ref and JNI types
context
Change imports of golang.org/x/net/context to context
egl
Fixes initializers of EGLDisplay
eglconf
Fixes initializers of EGLConfig
gotypes
Change imports of golang.org/x/tools/go/{exact,types} to go/{constant,types}
jni
Fixes initializers of JNI's jobject and subtypes
netipv6zone
Adapt element key to IPAddr, UDPAddr or TCPAddr composite literals.
https://codereview.appspot.com/6849045/
printerconfig
Add element keys to Config composite literals.下面舉個例子,源代碼中用到了golang.org/x/net/context包
package main
import (
"fmt"
"golang.org/x/net/context"
)
func main() {
background := context.Background()
fmt.Println(background.Err())
}使用go fix修正,將其替換為標准庫中的context包,我們可以如下命令來進行替換
$ go fix -fix context main.go也可以不替換,看看前後文件變化。
$ go tool fix -r context -diff main.go
main.go: fixed context
diff main.go fixed/main.go
--- main.go
+++ fixed/main.go
@@ -1,8 +1,8 @@
package main
import (
+ "context"
"fmt"
- "golang.org/x/net/context"
)
func main() {go 語言誕生了十多年只有九個可用的替換參數,可見兼容性保持的還算可以。
fmt
fmt命令是 go 語言自帶的格式化工具,用於格式化 go 源代碼文件。
$ go fmt -h
usage: go fmt [-n] [-x] [packages]
Run 'go help fmt' for details.通過命令go doc gofmt查看其詳細文檔
$ go doc cmd/gofmt
Gofmt formats Go programs. It uses tabs for indentation and blanks for
alignment. Alignment assumes that an editor is using a fixed-width font.
Usage:
gofmt [flags] [path ...]
The flags are:
-d
Do not print reformatted sources to standard output.
If a file's formatting is different than gofmt's, print diffs
to standard output.
-e
Print all (including spurious) errors.
-l
Do not print reformatted sources to standard output.
If a file's formatting is different from gofmt's, print its name
to standard output.
-r rule
Apply the rewrite rule to the source before reformatting.
-s
Try to simplify code (after applying the rewrite rule, if any).
-w
Do not print reformatted sources to standard output.
If a file's formatting is different from gofmt's, overwrite it
with gofmt's version. If an error occurred during overwriting,
the original file is restored from an automatic backup.gofmt使用tab進行縮進,空格進行對齊,在默認情況下格式化後的代碼將會輸出到標准輸出中,並非覆蓋到原文件。go fmt命令實際上用到的是gofmt命令,它是一個獨立的二進制文件,位於GOROOT/bin目錄下。
$ ls $GOROOT/bin -1
go.exe*
gofmt.exe*給go fmt命令加上-n標志就可以知曉其將要執行的指令。
$ go fmt main.go
/golang/bin/gofmt.exe -l -w main.go可以看出go fmt其實就是是gofmt -l -w的別名,gofmt命令有以下參數
-d:輸出格式化前後的文件差異-e:輸出所有錯誤-l:輸出發生變化的文件名-r:應用格式化規則-s:嘗試簡化代碼-w:覆蓋源文件,如果發生錯誤就恢復備份
假設現在有如下源文件
$ cat main.go
package main
import "fmt"
func main() {
fmt.Println("hello world!")}通過-d參數可以預覽其變化
$ gofmt -d main.go
diff main.go.orig main.go
--- main.go.orig
+++ main.go
@@ -3,5 +3,5 @@
import "fmt"
func main() {
-fmt.Println("hello world!")}
-
+ fmt.Println("hello world!")
+}-l參數將會輸出將要修改的文件名
$ gofmt -l .
main.go如果存在語法錯誤的話,-e參數可以輸出的更詳細
$ gofmt -d -e main.go
main.go:6:27: missing ',' in argument list
main.go:6:28: expected operand, found newline
main.go:7:2: expected ')', found 'EOF'
main.go:7:2: expected ';', found 'EOF'
main.go:7:2: expected ';', found 'EOF'
main.go:7:2: expected '}', found 'EOF'
main.go:7:2: missing ',' in argument list-w會將修改應用到源文件中
$ gofmt -l -w .
main.go
$ cat main.go
package main
import "fmt"
func main() {
fmt.Println("hello world!")
}你可以發現作為一個格式化工具,gofmt完全沒有提供任何的自定義配置,而專為美化 js 代碼的格式化器prettify它就提供了相當多的配置用於格式化代碼,這裡可以體現出 go 官方的一個態度,別想搞什麼個性化,所有人代碼風格最好都是一致的,至少有一個好處就是在閱讀代碼的時候不用去適應他人的習慣。不過其實它還是保留了一個自定義項的,就是格式化代碼的替換規則,規則是可以自定義的,格式如下
pattern -> replacement比如去除冗余的括號
(a) -> a查看文件變更
$ gofmt -r "(a) -> a" -d -l .
main.go
diff main.go.orig main.go
--- main.go.orig
+++ main.go
@@ -3,5 +3,5 @@
import "fmt"
func main() {
- fmt.Println(("hello world!"))
+ fmt.Println("hello world!")
}可以看到gofmt會將冗余的括號刪除掉。
get
get命令絕對是 go 開發過程中最常用的了,它的作用是將指定地址包源代碼下載到GOMODCACHE所對應的目錄中。
$ go get -h
usage: go get [-t] [-u] [-v] [build flags] [packages]
Run 'go help get' for details.-u:嘗試更新包的次要版本以及補丁版本,如果涉及到主版本的改變,比如v1->v2,將不會更新。-t:更新測試中的依賴版本-v:輸出被編譯的包,實際上屬於build flags的參數之一
在古早時期,go get的作用和go install類似,它會下載並編譯這些包,然而隨著 go 模塊的誕生與完善,這一部分的作用漸漸的被廢棄了,get命令現在最常用的作用就是對 go 模塊下載並解析依賴,所以你可以看到go get命令還支持build flags這類構建標志,並且如果你嘗試在模塊外像使用go install一樣使用go get,它會提示你此用法已經被廢棄了。
$ go get github.com/wire/wire
go: go.mod file not found in current directory or any parent directory.
'go get' is no longer supported outside a module.
To build and install a command, use 'go install' with a version,
like 'go install example.com/cmd@latest'
For more information, see https://golang.org/doc/go-get-install-deprecation
or run 'go help get' or 'go help install'.至於為什麼在文檔描述中還保留這些也是不得而知,翻看get命令的源代碼,你還會發現它保留了以前的那些 flag。
var (
getD = CmdGet.Flag.Bool("d", true, "")
getF = CmdGet.Flag.Bool("f", false, "")
getFix = CmdGet.Flag.Bool("fix", false, "")
getM = CmdGet.Flag.Bool("m", false, "")
getT = CmdGet.Flag.Bool("t", false, "")
getU upgradeFlag
getInsecure = CmdGet.Flag.Bool("insecure", false, "")
// -v is cfg.BuildV
)回到正題,get命令會將指定的包的源代碼下載到本地的全局依賴目錄中,也就是GOCACHE對應的目錄,然後將信息記錄到go.mod和go.sum文件中,前者負責記錄版本,後者負責記錄 sha1 校驗和確保安全性。get命令實際上是基於 VCS,也就是本地的版本控制系統,總共支持下面幾個
- git
- hg (Mercurial)
- bzr (Bazaar)
- svn
- fossil
其中,默認只支持 git 和 hg,可以GOVCS中進行配置,格式如下
GOVCS=github.com:git,example.com:hg,*:git|hg,*:allGOVCS僅支持 git 和 hg 作為 VCS,其它三個需要在GOPRIVATE中配置。
go get命令總共有下面幾種用法,可以直接將依賴地址作為參數
$ go get golang.org/x/net也可以指定版本
$ go get golang.org/x/net@0.17.0指定最新版本
$ go get golang.org/x/net@latest嘗試更新版本
$ go get -u golang.org/x/net移除某一依賴
$ go get golang.org/x/net@none上面這些是用來管理普通的依賴,它還可以用來管理不那麼普通的依賴,比如更新 go 語言的版本
$ go get go@latest
go: updating go.mod requires go >= 1.21.3; switching to go1.21.3
go: downloading go1.21.3 (windows/amd64)
go: upgraded go 1.21.0 => 1.21.3甚至還可以用來更新 go 工具鏈的版本
$ go get toolchain@latest當你使用go get更新 go 和工具鏈版本時,它們會在GOMODCACHE/golang.org/目錄下安裝新版本的 go
$ ls $(go env GOMODCACHE)/golang.org -1
'toolchain@v0.0.1-go1.21.3.windows-amd64'/
x/這時候再手動修改一下GOROOT就可以切換到指定的版本了。
install
install命令與get命令類似,它們都是用於下載第三方的依賴,不過區別在於get命令下載的是源碼,而install命令會將源碼編譯成本機可執行的二進制文件,二進制文件存放路徑首先在GOBIN目錄下,其次是GOPATH/bin。該命令的主要功能是用來下載第三方公開的一些命令行工具,得益於 go 語言的編譯速度和可移植性,並不需要下載二進制文件,而是直接下載源代碼然後在本地進行編譯。
$ go install -h
usage: go install [build flags] [packages]
Run 'go help install' for details.install命令接收構建標志和包名作為參數,在 gomod 開啟的情況下,包名必須攜帶版本號。例如下載 delve 調試器
$ go install -x github.com/go-delve/delve/cmd/dlv@latest
# get https://goproxy.cn/github.com/@v/list
# get https://goproxy.cn/github.com/go-delve/delve/cmd/@v/list
# get https://goproxy.cn/github.com/go-delve/delve/cmd/dlv/@v/list
# get https://goproxy.cn/github.com/go-delve/delve/@v/list
# get https://goproxy.cn/github.com/go-delve/@v/list
# get https://goproxy.cn/github.com/@v/list: 404 Not Found (0.014s)
# get https://goproxy.cn/github.com/go-delve/delve/cmd/@v/list: 404 Not Found (0.027s)
# get https://goproxy.cn/github.com/go-delve/delve/cmd/dlv/@v/list: 404 Not Found (0.027s)
# get https://goproxy.cn/github.com/go-delve/delve/@v/list: 200 OK (0.027s)
# get https://goproxy.cn/github.com/go-delve/@v/list: 404 Not Found (0.027s)
WORK=/home/user/tmp/go-build2033992495
mkdir -p $WORK/b001/
cat >/home/user/tmp/go-build2033992495/b001/importcfg.link << 'EOF' # internal
packagefile github.com/go-delve/delve/cmd/dlv=/home/user/.cache/go-build/f1/f11d552287458c0fce625abe50bf928c487064c36bbb1251ad8b1968772c3e4b-d
......
......
mkdir -p /home/wyh/gomod/bin/
mv $WORK/b001/exe/a.out /home/wyh/gomod/bin/dlv
rm -r $WORK/b001/它首先會將源代碼下載到GOMODCACHE所存放的路徑,這一點跟get命令一致,然後切換到臨時工作目錄,對其進行編譯,編譯完成後將二進制文件移動到GOPATH/bin目錄下,最後再刪除臨時文件夾。install命令還有一個限制就是下載的包必須是該項目的入口包,也就是說必須要包含main.go入口文件,否則的話會提示你無法安裝。例如,使用go install下載 gin
$ go install -x github.com/gin-gonic/gin@latest
# get https://goproxy.cn/github.com/@v/list
# get https://goproxy.cn/github.com/gin-gonic/gin/@v/list
# get https://goproxy.cn/github.com/gin-gonic/@v/list
# get https://goproxy.cn/github.com/@v/list: 404 Not Found (0.022s)
# get https://goproxy.cn/github.com/gin-gonic/gin/@v/list: 200 OK (0.027s)
# get https://goproxy.cn/github.com/gin-gonic/@v/list: 404 Not Found (0.028s)
package github.com/gin-gonic/gin is not a main packagegin 是一個 web 框架依賴庫,並不是一個命令行工具,自然也就沒有入口文件,所以也就會安裝失敗。
list
list命令會列出指定位置的包,一行一個,並且支持自定義格式化輸出,支持很多的參數,使用它的前提是必須在一個支持 gomod 的項目內。
$ go list -h
usage: go list [-f format] [-json] [-m] [list flags] [build flags] [packages]
Run 'go help list' for details.它支持的參數如下
-f:格式化參數-json:json 格式輸出-compiled:展示所有會被編譯器編譯的包-deps:展示每一個包及其所依賴的每一個包的名稱-test:展示每一個包的測試包-e:遇到錯誤的包時正常輸出-find:不解析這些包的依賴關系-export:使用該參數時,設置結構體Package.Export字段值為包含指定包的最新的導出信息的文件,以及設置Package.BuildID字段值為包的BuildID,主要是格式化輸出用。
模塊信息參數,
-m:輸出模塊而不是輸出包-versions:展示一個模塊所有可用的信息-retracted:展示一個模塊的撤回版本
[packages]參數可以是一個指定的包名,或者文件夾,也可以是all,表示任何地方,當使用-m參數時,all表示當前模塊引用的所有依賴。
例如,當前文件只有一個main.go文件,且只有一行輸出"hello world"的代碼,執行go list -deps .後,它輸出了從當前項目到fmt及其引用的所有依賴的包。
$ ls
go.mod go.sum main.go
$ cat main.go
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
$ go list -deps .
internal/goarch
unsafe
internal/abi
internal/unsafeheader
internal/cpu
internal/bytealg
internal/coverage/rtcov
internal/godebugs
internal/goexperiment
internal/goos
runtime/internal/atomic
runtime/internal/math
runtime/internal/sys
runtime
......
......
path
io/fs
os
fmt
golearn或者輸出當前項目下所有的模塊依賴
$ go list -m all
golearn
cloud.google.com/go v0.26.0
github.com/246859/containers v0.0.1
github.com/246859/river v0.1.0 => D:\WorkSpace\Code\riverdb
github.com/BurntSushi/toml v0.3.1
github.com/Jleagle/steam-go v0.0.0-20230725082712-1053b441b1f2
github.com/Jleagle/unmarshal-go v0.0.0-20210227002040-694f544f9265
github.com/KyleBanks/depth v1.2.1
github.com/Microsoft/go-winio v0.6.1
github.com/PuerkitoBio/purell v1.1.1
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578
github.com/andeya/ameda v1.5.3
github.com/andeya/goutil v1.0.1
...format
list命令的輸出是以行為單位,每一個行輸出都是一個包。官方提供了可以讓我們自定義行輸出格式的參數-f,它所接受的值也就是template/text模板引擎包所定義的模板語法,例如下面的示例
-f "package {{ .Dir }} {{ .Name }}"每一個迭代的包都將以下面結構體的形式傳入,該結構體中的所有字段都可以作為模板參數。
type Package struct {
Dir string // directory containing package sources
ImportPath string // import path of package in dir
ImportComment string // path in import comment on package statement
Name string // package name
Doc string // package documentation string
Target string // install path
Shlib string // the shared library that contains this package (only set when -linkshared)
Goroot bool // is this package in the Go root?
Standard bool // is this package part of the standard Go library?
Stale bool // would 'go install' do anything for this package?
StaleReason string // explanation for Stale==true
Root string // Go root or Go path dir containing this package
ConflictDir string // this directory shadows Dir in $GOPATH
BinaryOnly bool // binary-only package (no longer supported)
ForTest string // package is only for use in named test
Export string // file containing export data (when using -export)
BuildID string // build ID of the compiled package (when using -export)
Module *Module // info about package's containing module, if any (can be nil)
Match []string // command-line patterns matching this package
DepOnly bool // package is only a dependency, not explicitly listed
DefaultGODEBUG string // default GODEBUG setting, for main packages
// Source files
GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
CgoFiles []string // .go source files that import "C"
CompiledGoFiles []string // .go files presented to compiler (when using -compiled)
IgnoredGoFiles []string // .go source files ignored due to build constraints
IgnoredOtherFiles []string // non-.go source files ignored due to build constraints
CFiles []string // .c source files
CXXFiles []string // .cc, .cxx and .cpp source files
MFiles []string // .m source files
HFiles []string // .h, .hh, .hpp and .hxx source files
FFiles []string // .f, .F, .for and .f90 Fortran source files
SFiles []string // .s source files
SwigFiles []string // .swig files
SwigCXXFiles []string // .swigcxx files
SysoFiles []string // .syso object files to add to archive
TestGoFiles []string // _test.go files in package
XTestGoFiles []string // _test.go files outside package
// Embedded files
EmbedPatterns []string // //go:embed patterns
EmbedFiles []string // files matched by EmbedPatterns
TestEmbedPatterns []string // //go:embed patterns in TestGoFiles
TestEmbedFiles []string // files matched by TestEmbedPatterns
XTestEmbedPatterns []string // //go:embed patterns in XTestGoFiles
XTestEmbedFiles []string // files matched by XTestEmbedPatterns
// Cgo directives
CgoCFLAGS []string // cgo: flags for C compiler
CgoCPPFLAGS []string // cgo: flags for C preprocessor
CgoCXXFLAGS []string // cgo: flags for C++ compiler
CgoFFLAGS []string // cgo: flags for Fortran compiler
CgoLDFLAGS []string // cgo: flags for linker
CgoPkgConfig []string // cgo: pkg-config names
// Dependency information
Imports []string // import paths used by this package
ImportMap map[string]string // map from source import to ImportPath (identity entries omitted)
Deps []string // all (recursively) imported dependencies
TestImports []string // imports from TestGoFiles
XTestImports []string // imports from XTestGoFiles
// Error information
Incomplete bool // this package or a dependency has an error
Error *PackageError // error loading package
DepsErrors []*PackageError // errors loading dependencies
}
type PackageError struct {
ImportStack []string // shortest path from package named on command line to this one
Pos string // position of error (if present, file:line:col)
Err string // the error itself
}如果迭代的是模塊,則以下面結構體的形式傳入,它的所有字段也可以作為模板參數。
type Module struct {
Path string // module path
Query string // version query corresponding to this version
Version string // module version
Versions []string // available module versions
Replace *Module // replaced by this module
Time *time.Time // time version was created
Update *Module // available update (with -u)
Main bool // is this the main module?
Indirect bool // module is only indirectly needed by main module
Dir string // directory holding local copy of files, if any
GoMod string // path to go.mod file describing module, if any
GoVersion string // go version used in module
Retracted []string // retraction information, if any (with -retracted or -u)
Deprecated string // deprecation message, if any (with -u)
Error *ModuleError // error loading module
Origin any // provenance of module
Reuse bool // reuse of old module info is safe
}
type ModuleError struct {
Err string // the error itself
}查看所有包
$ go list -f "package {{.Dir}} {{.Name}}" ./...
package /golearn main
package /golearn/app cmd
package /golearn/cmd cmd
package /golearn/docs docs
package /golearn/tool tool
package /golearn/tool_test tool查看模塊
$ go list -m -f "mod {{.Path}} {{.Version}} {{.GoVersion}} {{.GoMod}}"
mod golearn 1.21.3 /golearn/go.modmod
go mod是專用於管理 go 模塊的命令。
$ go mod help
Go mod provides access to operations on modules.
Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.
Usage:
go mod <command> [arguments]
The commands are:
download download modules to local cache
edit edit go.mod from tools or scripts
graph print module requirement graph
init initialize new module in current directory
tidy add missing and remove unused modules
vendor make vendored copy of dependencies
verify verify dependencies have expected content
why explain why packages or modules are needed
Use "go help mod <command>" for more information about a command.它有下面幾個子命令
download:將go.mod文件中所有標明的依賴下載到本地緩存edit:編輯go.mod文件,它提供的命令行接口主要是提供給其它工具或腳本調用的。init:在當前目錄初始化一個 gomod 項目tidy:下載缺失的依賴,刪除無用的依賴graph:輸出依賴圖verify:驗證本地的依賴why:解釋為什麼會依賴這些模塊vendor:導出項目依賴到 vendor 目錄
init
$ go help mod init
usage: go mod init [module-path]init命令用於初始化一個 gomod 項目,其唯一的參數是模塊路徑,日後如果別人要下載你的依賴就需要通過此模塊路徑來作為依據。它的命名規則一般為
domain_name/user_name/repo_name比如一般大家都會把項目放在 github 上,所以可以是
github.com/jack/gotour不太建議使用一些特殊符號作為模塊路徑。下面看一個使用案例
$ mkdir gotour
$ cd gotour
$ go mod init "github.com/jack/gotour"
go: creating new go.mod: module github.com/jack/gotourtidy
$ go help mod tidy
usage: go mod tidy [-e] [-v] [-x] [-go=version] [-compat=version]tidy命令會清除go.mod中的無用依賴項,也就是沒有被引用的依賴項,以及會下載哪些了被引用但是不存在的依賴項。它支持以下參數
-v,輸出那些被刪除的模塊依賴-e,如果過程中發生錯誤則忽略它繼續執行-x,輸出執行過程-go=version,更新go.mod文件中的 go 版本-compact=version,保留從指定的主要 Go 版本中所需的任何附加校驗和,以便成功加載模塊圖,並且如果該版本的go命令從不同模塊版本中加載任何已導入的包,將導致 tidy 出錯。通常很少會用到這個參數,一般在版本版本變更時才會出錯,可以前往 stackoverflow 看看這個回答go modules - go mod tidy error message: "but go 1.16 would select" - Stack Overflow
看一個使用例子
$ go mod tidy -v
unused github.com/246859/containers
unused github.com/246859/river
unused github.com/Jleagle/steam-go
unused github.com/Jleagle/unmarshal-go
unused github.com/KyleBanks/depth
unused github.com/Microsoft/go-winio
unused github.com/PuerkitoBio/purell
unused github.com/PuerkitoBio/urlesc
unused github.com/andeya/ameda
unused github.com/andeya/goutil
unused github.com/asaskevich/govalidator
unused github.com/buger/jsonparser
unused github.com/bwmarrin/snowflake
unused github.com/bytedance/go-tagexpr/v2
unused github.com/bytedance/sonic
unused github.com/cespare/xxhash/v2
unused github.com/chenzhuoyu/base64x
......download
$ go help mod download
usage: go mod download [-x] [-json] [-reuse=old.json] [modules]download命令的名稱雖然翻譯過來叫下載,但它只是把依賴下載到本地的依賴緩存中,不會修改go.mod文件,它的作用是預下載依賴到本地的文件緩存中,如果你想要下載某一個依賴,建議使用go get或者go mod tidy。
下面是幾個使用例子
$ go mod download -x gorm.io/gorm
# get https://goproxy.cn/gorm.io/gorm/@v/list
# get https://goproxy.cn/gorm.io/gorm/@v/list: 200 OK (0.084s)如果不帶任何參數,它會下載所有存在於go.mod文件中但是又不存在與本地依賴緩存中的依賴項,如果沒有需要下載的它會輸出
go: no module dependencies to downloadedit
$ go help mod edit
usage: go mod edit [editing flags] [-fmt|-print|-json] [go.mod]edit是一個命令行接口,用於修改go.mod文件,通常是提供給其它程序使用的,一些編輯器 IDE 為提供 gomod 支持就會使用這些命令。它支持下面幾個參數
-module,修改模塊路徑-go=version,修改期望的 go 版本-require=path@version,新增一個依賴項-droprequire=path@version,刪除一個依賴項-exclude=path@version,新增一個排除依賴項-dropexclude=path@version,刪除一個排除依賴項-replace=old@version=new@version,新增一個替換依賴項-dropreplace=old@version,刪除一個替換依賴項-retract=version,新增一個版本回退項-dropretract=version,刪除一個版本回退項
還有一些其它用於展示的參數
-print,輸出文件內容-json,以 json 格式輸出
比如下面這個例子
$ go mod edit -print
module golearn
go 1.21.3
require (
github.com/dstgo/task v1.2.0
github.com/spf13/cast v1.5.1
github.com/swaggo/swag v1.16.2
golang.org/x/net v0.19.0
gorm.io/gorm v1.25.5
)graph
$ go help mod graph
usage: go mod graph [-go=version] [-x]graph命令會輸出當前項目下的依賴圖,其可讀性很差,並且大多數時候也不是給人類閱讀的,其結果通常會被處理再以可視化的形式展示。每一行是就是一個依賴,其格式如下
引用方 被引用方比如
golearn go@1.21.3它還支持兩個參數
-go=version,使用給定 go 版本加載依賴圖,其值不能小於go.mod文件中的版本。-x,展示過程中所執行的命令。
看一個簡單的使用例子
$ go mod graph
golearn github.com/246859/containers@v0.0.1
golearn github.com/246859/river@v0.1.0
golearn github.com/Jleagle/steam-go@v0.0.0-20230725082712-1053b441b1f2
golearn github.com/Jleagle/unmarshal-go@v0.0.0-20210227002040-694f544f9265
golearn github.com/KyleBanks/depth@v1.2.1
golearn github.com/Microsoft/go-winio@v0.6.1
golearn github.com/PuerkitoBio/purell@v1.1.1
golearn github.com/PuerkitoBio/urlesc@v0.0.0-20170810143723-de5bf2ad4578
golearn github.com/andeya/ameda@v1.5.3
golearn github.com/andeya/goutil@v1.0.1
golearn github.com/asaskevich/govalidator@v0.0.0-20230301143203-a9d515a09cc2
golearn github.com/buger/jsonparser@v1.1.1
golearn github.com/bwmarrin/snowflake@v0.3.0
golearn github.com/bytedance/go-tagexpr/v2@v2.9.11
......vendor
$ go help mod vendor
usage: go mod vendor [-e] [-v] [-o outdir]vendor 是早期 gomod 沒有推出之前的一個 gopath 的替代方案,每一個 go 項目下都會有一個 vendor 目錄,按照domain/user/project這種格式單獨存放每一個項目的依賴,就像隔壁 nodeJs 臃腫的node_module一樣每一個項目的依賴分開放,這種依賴管理方式現在看起來確實很愚蠢,但是在那個時候確實沒有更好的方案了,之所以保留 vendor 是因為 go 秉承的向下兼容的承諾,有一些老項目包括 go 源代碼裡面可能還在使用 vendor。
回到正題,vendor是go mod的一個子命令,它可以將當前模塊所引用的全局依賴導出到 vendor 目錄中。
$ go mod vendor -h
usage: go mod vendor [-e] [-v] [-o outdir]
Run 'go help mod vendor' for details.它有以下幾個參數
-o:指定輸出路徑文件夾-v:輸出每一個依賴-e:出現錯誤時不退出仍然繼續
下面看一個示例,先用go list -m all查看下當前項目所引用的依賴
$ go list -m all
github.com/dstgo/task
github.com/davecgh/go-spew v1.1.1
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0
github.com/stretchr/objx v0.5.0
github.com/stretchr/testify v1.8.4
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
gopkg.in/yaml.v3 v3.0.1導出到當前的 vendor 目錄下
$ go mod vendor -v -e -o vendor
# github.com/davecgh/go-spew v1.1.1
## explicit
github.com/davecgh/go-spew/spew
# github.com/pkg/errors v0.9.1
## explicit
github.com/pkg/errors
# github.com/pmezard/go-difflib v1.0.0
## explicit
github.com/pmezard/go-difflib/difflib
# github.com/stretchr/testify v1.8.4
## explicit; go 1.20
github.com/stretchr/testify/assert
# gopkg.in/yaml.v3 v3.0.1
## explicit
gopkg.in/yaml.v3導出後的目錄結構如下
└─vendor
├─github.com
│ ├─davecgh
│ │ └─go-spew
│ │ └─spew
│ ├─pkg
│ │ └─errors
│ ├─pmezard
│ │ └─go-difflib
│ │ └─difflib
│ └─stretchr
│ └─testify
│ └─assert
└─gopkg.in
| └─yaml.v3
|
|--modules.txt其中的modules.txt是描述所有依賴項的文件,就類似於現在的go.mod。
verify
$ go help mod verify
usage: go mod verify該命令會檢查項目的依賴自下載到本地以後是否被修改過。比如,如果沒問題就輸出all modules verified
$ go mod verify
all modules verified否則它會報告哪裡發生了改變,並以非正常狀態結束命令。比如
$ go mod verify
gorm.io/gorm v1.25.5: dir has been modified (/go/mod/libs/gorm.io/gorm@v1.25.5)why
$ go help mod why
usage: go mod why [-m] [-vendor] packages...解釋為什麼這個包被依賴,實際上是輸出有關它的依賴圖。比如
$ go mod why gorm.io/gorm
# gorm.io/gorm
golearn
gorm.io/gorm默認只會解析從main的導入,加上-m參數可以分析每一個包的導入情況。
work
命令 work 是一個用於 go 多模塊管理的本地開發工具
$ go work help
Work provides access to operations on workspaces.
Note that support for workspaces is built into many other commands, not
just 'go work'.
The commands are:
edit edit go.work from tools or scripts
init initialize workspace file
sync sync workspace build list to modules
use add modules to workspace file
vendor make vendored copy of dependencies
Use "go help work <command>" for more information about a command.init
init子命令用於初始化一個 workspace,該命令會創建一個名為go.work的文件
$ go work init -h
usage: go work init [moddirs]
Run 'go help work init' for details.接收參數[moddirs]指定將哪些模塊納入管理,例如
$ go work init ./service ./apiuse
use子命令用於向go.work中添加納入管理的模塊目錄
$ go help work use
usage: go work use [-r] [moddirs]
Use provides a command-line interface for adding
directories, optionally recursively, to a go.work file.接收[moddirs]作為參數,還有一個-r表示在[moddirs]路徑下遞歸搜索模塊,例如
$ go work use -r ./oss-api ./multi_modulesedit
edit子命令的作用同go mod edit,都是留給命令行接口給其它工具和腳本操作的。
$ go help work edit
usage: go work edit [editing flags] [go.work]
Edit provides a command-line interface for editing go.work,
for use primarily by tools or scripts. It only reads go.work;
it does not look up information about the modules involved.
If no file is specified, Edit looks for a go.work file in the current
directory and its parent directories參數有如下
-fmt,格式化go.work文件-use,-dropuse,添加和移除模塊路徑-replace=old[@v]=new[@v],-dropreplace=old[@v]=new[@v],用於添加和移除要替換的模塊-go,-toolchain=name,指定 go 版本,以及指定要使用的工具鏈-print,將最後的修改打印出來,不寫回文件-json,以json格式輸出,無法與-print同時存在,對應類型結構如下所示gotype GoWork struct { Go string Toolchain string Use []Use Replace []Replace } type Use struct { DiskPath string ModulePath string } type Replace struct { Old Module New Module } type Module struct { Path string Version string }
一些使用示例如下,格式化輸出
$ go work edit -fmt -print
go 1.22.0
use (
./ab/cd
./auth
./user
)json 輸出
$ go work edit -fmt -json
{
"Go": "1.22.0",
"Use": [
{
"DiskPath": "./ab/cd"
},
{
"DiskPath": "./auth"
},
{
"DiskPath": "./user"
}
],
"Replace": null
}sync
sync子命令用於將go.work中的模塊列表回到 workspace 中的各個模塊中。
$ go help work sync
usage: go work sync
Sync syncs the workspace's build list back to the
workspace's modules這個過程主要發生在本地開發完成後,各個模塊已經完成發版工作,此時使用sync,它會根據各個模塊的依賴關系來更新 worksapce 所有模塊的go.mod中的依賴,從而不需要我們去手動更新。
vendor
vendor命令會將 workspace 中所有模塊依賴的庫做一份復制到vendor目錄下。
$ go work help vendor
usage: go work vendor [-e] [-v] [-o outdir]功能同go mod vendor,不再做過多的贅述。
vet
命令vet是一個 go 語言源代碼的靜態錯誤檢查工具,就像其它語言的 lint 工具,比如Eslint。
$ go vet -h
usage: go vet [build flags] [-vettool prog] [vet flags] [packages]
Run 'go help vet' for details.
Run 'go tool vet help' for a full list of flags and analyzers.
Run 'go tool vet -help' for an overview.先來看一個簡單的示例,現有如下源代碼
$ cat main.go
package main
import "fmt"
func main(){
fmt.Println("hello world!"
}在同級目錄下不帶任何參數執行go vet
$ go vet
vet: ./main.go:6:28: missing ',' before newline in argument list (and 1 more errors)vet會報告哪個文件哪一行出了什麼問題。它支持構建標志作為參數,例如-n和-x,支持包,文件夾,文件名作為參數。
$ go vet .
$ go vet main.go
$ go vet ./cmd
$ go vet runtime通過如下命令查看其更詳細的參數和解釋。
$ go tool vet help
vet is a tool for static analysis of Go programs.
vet examines Go source code and reports suspicious constructs,
such as Printf calls whose arguments do not align with the format
string. It uses heuristics that do not guarantee all reports are
genuine problems, but it can find errors not caught by the compilers.
Registered analyzers:
asmdecl report mismatches between assembly files and Go declarations
assign check for useless assignments
atomic check for common mistakes using the sync/atomic package
bools check for common mistakes involving boolean operators
buildtag check //go:build and // +build directives
......go tool vet命令並不能直接用來對代碼進行檢查,應該使用go vet。go vet參數中的[vet flag]支持設置代碼分析器,可用的值如下
asmdecl 檢查匯編文件是否與go聲明不匹配
assign 檢查是否有無用的變量
atomic 檢查使用sync/atomic時是否破壞了原子性
bools 檢查是否錯誤使用邏輯運算符
buildtag 檢查build tag
cgocall 檢查違反cgao指針傳遞規則的行為
composites 檢查未初始化的復合結構,比如map,chan
copylocks 檢查是否發生了鎖的值復制
directive 檢查go工具鏈指令
errorsas 檢查是否向errors.As傳遞非指針類型或非error類型的值
framepointer 檢查編譯優化後的匯編代碼是否在保存幀指針之前對其進行清除
httpresponse 檢查是否錯誤使用httpresponse
ifaceassert 檢查接口到接口的類型斷言
loopclosure 循環變量的引用問題
lostcancel context.WithCancel沒有使用cancel函數
nilfunc 檢查函數和nil之間是否存在無用的比較
printf 檢查printf的格式化參數是否正確
shift 檢查是否有等於或超過整數寬度的移位
sigchanyzer 檢查無緩沖的chan os.Signal
slog 檢查不合法的結構化日志調用
stdmethods 檢查已知接口方法的簽名是否正確
stringintconv 檢查字符串整型轉換
structtag 檢查結構體tag是否正確
testinggoroutine 檢查是否在測試中使用協程調用testing.Fatal
tests 檢查測試和示例的常見錯誤用法
timeformat 使用(time.Time).Format 或 time.Parse的時間格式是否正確
unmarshal 向unmarshal傳遞非指針或非接口類型
unreachable 檢查不可到達的代碼
unsafeptr 檢查uintptr到unsafe.Pointer不正確轉換
unusedresult 檢查未使用的函數返回值它們都是針對某一個點進行分析的分析器,比如timeformat分析器是檢查time.Format的調用格式是否符合正確的語法。在默認情況下以上所有的分析器都會啟用,單獨啟用可用使用如下的格式
$ go vet -timeformat main.go單獨禁用
$ go vet -timeformat=false main.go這些分析器的源代碼位於cmd/vendor/golang.org/x/tools/go/analysis/passes,每一個分析器都是 go 語言容易犯的一個坑,所以十分建議使用vet命令來檢查你的代碼。除此這些之外,它還支持一些其它的標志參數
-V,僅打印版本然後退出-json,以 json 形式輸出-c=n,顯示上下文中指定數目的沖突行(似乎並沒有任何作用)
還有一些外置的分析器,比如shadows,它負責檢測短變量命名的變量隱藏問題,由於是外置的所以需要用go install來進行下載
$ go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest使用格式如下
$ go vet -vettool=$(which shadow)test
$ go test -h
usage: go test [build/test flags] [packages] [build/test flags & test binary flags]
Run 'go help test' and 'go help testflag' for details.test命令是 go 語言工具鏈中提供測試功能的命令,這個功能相當的重要,對於一個軟件而言,完善的測試的是必不可少的。這裡只是簡單的介紹下如何使用test命令,如果要了解更多測試相關,前往:測試
它除了支持build命令的編譯參數之外,test還支持下面幾個參數
-args,程序的入口參數-c,編譯當前包的測試二進制文件到當前目錄但並不執行,以pkg.test方式命名-exec,在測試開始之前執行一些其它的命令-json,測試的輸出風格變為 json-o,指定測試二進制文件的路徑名
它還支持許多testflag ,使用help命令查看所有testflag
$ go help testflag
The 'go test' command takes both flags that apply to 'go test' itself
and flags that apply to the resulting test binary.
The following flags are recognized by the 'go test' command and
control the execution of any test:
-bench regexp
-benchtime t
-count n
......介紹幾個常用的
-v,輸出每一個用例的測試結果。-timeout duration,測試執行超時時間-skip regexp,跳過指定的測試用例-short,讓哪些運行時間很長的測試用例縮短運行時間-shuffle,打亂所有測試用例的執行順序-run regexp,運行指定的測試用例-list regexp,列出每一個測試用例-cpu 1,2,4,指定 cpu 數量-count n,指定每個測試用例執行多少次
最簡單的用法就是,不帶任何參數,它會執行當前所在包下的所有測試用例,並輸出結果。
$ ls *_test.go
hello_test.go
$ go test
PASS
ok golearn 0.522s指定某一個測試文件
$ go test hello_test.go
ok command-line-arguments 0.041s加上-v參數可以查看更詳細的輸出,它相當常用。
$ go test -v hello_test.go
=== RUN TestHello
hello_test.go:6: hello world!
--- PASS: TestHello (0.00s)
PASS
ok command-line-arguments 0.041s指定某一個測試用例
$ go test -v -run TestHello
=== RUN TestHello
hello_test.go:6: hello world!
--- PASS: TestHello (0.00s)
PASS
ok golearn 0.028s在測試時,test命令分兩種模式,先來講第一種文件夾模式,當不帶package參數執行test命令時,它就會以文件夾模式執行測試,比如下面幾個命令
$ go test
$ go test -v在這種模式下,禁用測試緩存。另一種模式是列表模式,當package參數不為空時,就會以列表模式進行測試,它與前者的區別就是是否開啟測試緩存。比如下面幾個
$ go test -v .
$ go test -v ./...
$ go test .
$ go test -v net/http在列表模式下,go 會將指定包下的每一個包的測試文件編譯成二進制文件並執行,為了避免重復運行測試,go 默認會將結果緩存,二次運行時不會重新編譯。使用下列參數時將會默認開啟緩存
-benchtime-cpu-list-parallel-runshort-timeout-failfast-v
使用除了這些參數之外的其它參數就可以關閉緩存,官方提倡的方法是使用-count=1的方式來禁用緩存。比如
$ go test -v -count=1 ./...指令
與命令不同,go 的指令是以硬編碼的形式存在於源文件中的,它們有另一個比較術語化的名字:編譯指示(progma directives)。
編譯器和鏈接器會因為它們改變自身的行為從而達到控制編譯的效果,就有點類似於 c 語言中的宏,當然並非所有指令都是用來影響編譯的,部分用於其它功能性行為,比如generate指令通常用於代碼生成的功能。這些指令通常以注釋的形式存在,並且以//go:作為前綴,中間不能包含任何的空格,比如//go:generate指令。所有指令類型總共分為兩種
- 功能性指令,這類是 go 提供的功能性指令可以隨意使用,比如
generate,embed,build。 - 編譯器指令,這類指令需要謹慎使用,胡亂使用可能導致無法預測的結果。
除了功能性指令外,大多數指令都只能作用於函數簽名上。對於編譯器指令可以執行命令go doc compile查看其指令。對於全部指令,可以在cmd/compile/internal/ir/node.go: 440找到有關它們的信息。
generate
$ go help generate
usage: go generate [-run regexp] [-n] [-v] [-x] [build flags] [file.go... | packages]generate指令顧名思義就是跟生成有關的,通常它的作用是用於執行那些會生成代碼以及更新源代碼的命令,不過實際上它可以執行任何命令。並且,generate指令與其它指令不同,它有一個專門的命令可以用於執行所有位於源文件中的 generate 指令。它可以以文件名或者包名來作為輸入參數來表示執行哪些文件的generate指令,下面是它的其它參數。
-run=regex,運行指定的 generate 指令-skip=regex,跳過指定的 generate 指令-n,打印將要執行的命令-x,打印過程中執行的命令-v,輸出處理的文件
除此之外,在generate指令中執行的命令還支持以下幾個內置參數
$GOARCH,cpu 架構$GOOS,操作系統$GOFILE,文件名$GOLINE,行號$GOPACKAGE,包名$GOROOT,go root$DOLLAR,美元符號$PATH,path 環境變量
看個例子,什麼代碼都沒有只有一行注釋
package main
//go:generate echo "hello world!"執行命令
$ go generate .
hello world!這個例子是執行 go 命令
package main
//go:generate go version執行命令
$ go generate .
go version go1.21.3 windows/amd64generate指令可以用於執行任何命令,比如 swagger 生成 API 文檔,或者 Wire 生成 IOC 代碼。不過這個指令不適合執行特別復雜的命令,它適合執行簡短的命令,如果有復雜的需求可以使用腳本或者 makefile 來代替。
embed
embed指令是 1.16 新增的,它的作用是將可以將靜態文件一同打包進二進制文件中,比如 HTML 模板之類的。它的格式如下
//go:embed patternpattern可以是 glob 表達式,也可以是文件夾或者某一個具體文件。看一個例子
package main
import "embed"
//go:embed *
var static embed.FSembed指令要求必須位於一個類型為embed.Fs的全局變量上方,注意必須是全局變量,並且使用它必須導入embed包,在這個例子中,*代表了會將當前文件夾下的所有文件都打包進二進制文件中,不過它不會允許.開頭的文件夾存在。
下面這個例子展示了從嵌入的文件中讀取內容
package main
import (
"embed"
"fmt"
)
//go:embed *.txt
var static embed.FS
func main() {
bytes, err := static.ReadFile("hello.txt")
if err != nil {
panic(err)
}
fmt.Println(string(bytes))
}它只有三個方法,使用起來跟平常文件系統沒什麼區別,並且由於它實現了io/Fs接口,所以也可以作為Fs對象來進行傳遞。
func (f FS) Open(name string) (fs.File, error)
func (f FS) ReadFile(name string) ([]byte, error)
func (f FS) ReadDir(name string) ([]fs.DirEntry, error)下面這個例子展示了通過embed指令嵌入 html 文件,並通過 http 服務訪問。
package main
import (
"embed"
"net/http"
)
//go:embed index.html
var htmlFs embed.FS
func main() {
http.Handle("/", http.FileServer(http.FS(htmlFs)))
http.ListenAndServe(":8080", http.DefaultServeMux)
}訪問結果如下
$ curl -s -GET 127.0.0.1:8080
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello world!</title>
</head>
<body>
<H1>Hello World!</H1>
<script>
alert("hello world!");
</script>
</body>
</html>embed指令還支持全局變量的類型可以為[]byte,比如下面這個例子
package main
import (
_ "embed"
"net/http"
)
//go:embed index.html
var rawdata []byte
func main() {
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
writer.Write(rawdata)
})
http.ListenAndServe(":8080", http.DefaultServeMux)
}它實現的效果跟上一個例子是差不多的。
$ curl -s -GET 127.0.0.1:8080
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello world!</title>
</head>
<body>
<H1>Hello World!</H1>
<script>
alert("hello world!");
</script>
</body>
</html>build
在build-編譯控制部分,講到了用// +build指令來控制編譯行為。而//go:build指令是在 1.17 新出的,意在取代先前的指令,不過現在都 1.21 也還沒有取代,估計以後會以共存的方式存在,關於這個新指令,官方文檔也有介紹:build constraints。它的功能與前者沒什麼區別,但是語法更加嚴格,支持布爾表達式,看一個例子
//go:build (linux && 386) || (darwin && !cgo)
package pkg_name這種方式可讀性要比原來那種高得多。
line
line指令會影響其下一行的行號,列號,已經文件名,它的作用僅限於此,大部分時候可能會用來調試錯誤之類的。比如在發生錯誤時,會改變編譯器輸出的信息。
package main
var a undefinedType
func main() {
}正常情況下,編譯器會輸出
.\main.go:3:7: undefined: undefinedType但如果使用了line指令,就不一樣了
package main
//line abc.go:10:100
var a undefinedType
func main() {
}那麼它的輸出就是
abc.go:10:106: undefined: undefinedType並且因為歷史遺留原因,line指令也是唯一一個用法跟其它指令不同的指令。它的格式是
//line filename:line:column可以看到它並不需要go:作為前綴。
linkname
這個指令的操作可以用於鏈接其它包的函數或者全局變量,即便它是私有類型,這種操作經常在標准庫尤其是runtime中出現,有一些函數沒有函數體就是通過這種方式來實現的,另一部分空函數體的函數則是由匯編實現。來看下它的用法,使用格式如下
//go:linkname 鏈接類型名稱 被鏈接的類型並且在使用之前,比如導入unsafe包。看一個簡單的鏈接標准庫中的私有類型的例子
import (
"fmt"
"unsafe"
)
//go:linkname memhash runtime.memhash
func memhash(p unsafe.Pointer, h, s uintptr) uintptr
func MemHash(data []byte) uint64 {
ptr := unsafe.Pointer(unsafe.SliceData(data))
return uint64(memhash(ptr, 0, uintptr(len(data))))
}
func main() {
fmt.Println(MemHash([]byte("hello")))
}輸出
15395306441938000233它將runtime.memhash這個私有函數與我們自己聲明的函數鏈接到了一起,這個函數沒有函數體只有一個簽名,只起到一個載體的作用。memhash的作用是給定一個指針,哈希種子,和內存偏移量,根據內存來計算哈希值。這個鏈接過程在編譯期完成,
如果不是標准庫的,那麼情況就有些不一樣了,比如在example包下有一個test函數,在鏈接之前首先要匿名導入這個包。
package example
// 一個私有類型,外界無法訪問。
func test() string {
return "a"
}package main
import (
"fmt"
_ "golearn/example"
_ "unsafe"
)
//go:linkname test golearn/example.test
func test() string
func main() {
fmt.Println(test())
}輸出
a可以看到已經鏈接成功了,這種方法可以繞過 go 的模塊系統為所欲為,不過不建議大規模使用,除非你知道你自己干什麼。
noinline
noinline指令表示一個函數禁止內聯優化,即便它非常簡單。看一個簡單的例子
package main
func val() string {
return "val"
}
func main() {
var c = val()
_ = c
}val是一個非常簡單的函數,它的作用是返回一個字符串字面量,由於它太過簡單且結果總是可以預測,那麼在編譯時它會被編譯器優化成如下形式
package main
func main() {
var c = "val"
_ = c
}來看看它匯編的樣子,可以看到並沒有發現val函數的調用。
TEXT main.val(SB), NOSPLIT|NOFRAME|ABIInternal, $0-0
FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
LEAQ go:string."val"(SB), AX
MOVL $3, BX
FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
RET接下來加上noinline指令
package main
//go:noinline
func val() string {
return "val"
}
func main() {
var c = val()
_ = c
}再來看看其匯編形式
CMPQ SP, 16(R14)
PCDATA $0, $-2
JLS 17
PCDATA $0, $-1
PUSHQ BP
MOVQ SP, BP
FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
PCDATA $1, $0
CALL main.val(SB)
POPQ BP
RET這次可以非常明顯的看到了main.val這個調用,而這也正是noinline指令發揮的功能,阻止編譯器優化時的函數內聯。
nospilit
nospilit指令的作用是跳過棧溢出檢測。由於 go 的並發調度模型是搶佔式調度,假設一個函數會運行非常底層的代碼,其它協程在調用此函數時不適合被搶佔,就可以使用該指令來表示。
//go:nosplit
func nospilitFn()使用該指令後,也就不會再進行棧增長。
noescape
noescape,通過它的名字可以很容易猜到是跟逃逸有關的,它的作用是表示當前函數不會發生內存逃逸行為,執行完後所有資源都被回收,並且這個函數必須只有簽名沒有函數體,在這種情況下一般函數的實現是由匯編實現的。
比如之前用到的memhash就會用到這個指令
//go:noescape
//go:linkname memhash runtime.memhash
func memhash(p unsafe.Pointer, h, s uintptr) uintptr這樣一來,編譯器不會對其進行逃逸分析,前提是你得保證它不會發生逃逸,如果發生了,那就不知道會有什麼後果了。
uintptrescapes
uintptrescapes指令表示該函數中uinptr類型的參數被轉換為了指針值並且逃逸到了堆上,且必須保持其存活。這個指令一般用於一些低級的系統調用,大部分情況下不需要去了解它。
//go:uintptrescapes
func nospilit(ptr uintptr) uintptr在以前應該還有一個notinheaps指令用於表示一個類型不允許分配內存到堆上,不知道在哪個版本被刪掉了。
norace
norace指令表示一個函數的內存訪問不再需要競態分析,通常是在運行低層次代碼不適合進行競態分析時使用。
//go:norace
func low_level_code(ptr uintptr) uintptrTIP
還有部分指令限制了只能由runtime包使用,外部是無法使用的, 它們會涉及到更深的東西,想要了解可以在Runtime-only compiler directives中看到有關它們的介紹。
