科技改變生活 · 科技引領未來
寫在前面go語言開發者在日常工作或學習中,用到最多的命令可能是gobuild、gorun、goinstall、goget...,通過這些命令可以幫我們編譯程序、運行程序、安裝程序和獲取代碼包,然而在執行這些命令的時候,你是否思考過go編譯過
寫在前面
go語言開發者在日常工作或學習中,用到最多的命令可能是go build、go run、go install、go get...,通過這些命令可以幫我們編譯程序、運行程序、安裝程序和獲取代碼包,然而在執行這些命令的時候,你是否思考過go編譯過程中是如何組織我們的源碼文件的?go install的時候發生了什么?以及go get只是去下載我們依賴的包文件嗎?go還有哪些實用的命令?這是一篇可以說比較基礎的文章,也可以作為go命令的一個速查手冊,旨在幫助我們更深刻的了解go。
幾個概念
gopath可能是每個go開發者最熟悉不過的東西了,gopath就是我們的工作區,通過go env可以查看到我們的gopath,如 GOPATH="/Users/code/go",gopath就是我們的工作區,gopath下面一般會建立bin、src、pkg三個文件夾。
export GOBIN=/tmp //聲明GOBIN目錄 go install $GOPATH/src/code //安裝code程序 /tmp/code 發現程序已經被安裝到了GOBIN下
常見輔助命令
go有一些輔助命令可以幫助我們更好的理解go的一些執行過程,輔助命令的體現就是go run/build.. -x xx.go, -x就是輔助命令。-x可以有以下類型:
小眾輔助命令
編譯
go run
我們通過go run -n main.go來看下構建的過程
mkdir -p $WORK/b001/ cat >$WORK/b001/importcfg.link << 'EOF' # internal packagefile command-line-arguments=/Users/gopher/Library/Caches/go-build/f5/f58206d3722b7787f6341e5013f38f3887e44138ffbdafbb07b67da377632762-d packagefile code/utils=/Users/gopher/Library/Caches/go-build/89/8931b87411d98f32ea6222ad605f3367700be896828286782db3e51de73217ff-d packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a ... EOF mkdir -p $WORK/b001/exe/ cd . /usr/local/go/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/main -importcfg $WORK/b001/importcfg.link -s -w -buildmode=exe -buildid=ttUV5epeZDG7q3yVlG2A/ek8iSJ8rjD-RiujnYKAd/hNomEMXuvdA3I1ks6XOE/ttUV5epeZDG7q3yVlG2A -extld=clang /Users/goper/Library/Caches/go-build/f5/f58206d3722b7787f6341e5013f38f3887e44138ffbdafbb07b67da377632762-d $WORK/b001/exe/main
通過 go run -work main.go,我們來看看臨時文件夾:
go run -work main.go WORK=/var/folders/s4/2cpbmp4s1_j4y3zv2s08m9q40000gn/T/go-build281107053
切到臨時目錄:
└── b001 ├── exe │ └── main └── importcfg.link
默認情況下,go run命令運行完后會刪除臨時文件夾。
go build
go build用于編譯我們的程序,默認編譯后的文件存放在當前的文件夾中,如果指定-o那么就可以移動到指定的文件中。我們通過go build -n main.go來看下build的過程:
# # command-line-arguments # mkdir -p $WORK/b001/ cat >$WORK/b001/_gomod_.go << 'EOF' # internal package main import _ "unsafe" //go:linkname __debug_modinfo__ runtime.modinfo var __debug_modinfo__ = "0wxaffx92tbx02Axe1xc1axe6xd6x18xe6pathtcommand-line-argumentsnmodtcodet(devel)tnxf92C1x86x18 rx00x82Bx10Ax16xd8xf2" EOF cat >$WORK/b001/importcfg << 'EOF' # internal # import config packagefile code/utils=/Users/sunkang/Library/Caches/go-build/89/8931b87411d98f32ea6222ad605f3367700be896828286782db3e51de73217ff-d packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a packagefile runtime=/Users/sunkang/Library/Caches/go-build/b4/b44856e241a6bb3baf596eb19e4566e956a490ef403c1ed31ba8f014542fcf81-d EOF cd /Users/gopher/go/src/code /usr/local/go/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -lang=go1.15 -complete -buildid Fjkl2yr7MirhGqbO0lrl/Fjkl2yr7MirhGqbO0lrl -goversion go1.15.3 -D _/Users/sunkang/go/src/code -importcfg $WORK/b001/importcfg -pack -c=4 ./main.go $WORK/b001/_gomod_.go /usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal cat >$WORK/b001/importcfg.link << 'EOF' # internal packagefile command-line-arguments=$WORK/b001/_pkg_.a packagefile code/utils=/Users/sunkang/Library/Caches/go-build/89/8931b87411d98f32ea6222ad605f3367700be896828286782db3e51de73217ff-d packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a ... EOF mkdir -p $WORK/b001/exe/ cd . /usr/local/go/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=s0BcVGdGaAeuHGFL7teJ/Fjkl2yr7MirhGqbO0lrl/Fjkl2yr7MirhGqbO0lrl/s0BcVGdGaAeuHGFL7teJ -extld=clang $WORK/b001/_pkg_.a /usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal mv $WORK/b001/exe/a.out main
整體流程和go run差不多,唯一不同的是在compile和link之后,生成的可執行文件會移動到當前文件夾中,并不是隨著臨時文件夾一起消亡。
go install
go install用于編譯并安裝指定的代碼包及它們的依賴包,當指定的代碼包的依賴包還沒編譯安裝的時候,會先去安裝依賴包。與go build不同的是,go install會把編譯后的安裝包放在指定的文件夾中。安裝的代碼包會在當前工作區的pkg目錄下,即.a的歸檔文件,當我們沒有設置GOBIN時,安裝的命令源碼文件會存放在當前工作區的bin目錄下,當我們設置了GOBIN時,則會放在GOBIN下。 假設現在項目是這樣的:
├── go.mod ├── main.go └── utils └── utils.go
main.go就是我們的入口文件,即命令源碼文件,utils是我們的依賴包,即庫源碼文件。 當我們只在當前目錄執行go install -n main.go后:
mkdir -p $WORK/b001/ cat >$WORK/b001/importcfg.link << 'EOF' # internal ... EOF mkdir -p $WORK/b001/exe/ cd . /usr/local/go/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=yfU8nbngCa6KbgUlJLKa/Fjkl2yr7MirhGqbO0lrl/khh18opCAdXA909bR95q/yfU8nbngCa6KbgUlJLKa -extld=clang /Users/sunkang/Library/Caches/go-build/ed/ed5868e69c99c66b8bf4b399e989ea410063b44143b546fd3f4a98f758d73a47-d /usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal mkdir -p /Users/gopher/go/bin/ mv $WORK/b001/exe/a.out /Users/gopher/go/bin/main
可以發現最后一行與go build的不同的是,它把可執行文件移動到了GOPATH/bin中。當我們切到依賴包utils文件中執行 go install -n后:
mkdir -p $WORK/b001/ mkdir -p /Users/gopher/go/pkg/darwin_amd64/code/ mv /Users/gopher/Library/Caches/go-build/89/8931b87411d98f32ea6222ad605f3367700be896828286782db3e51de73217ff-d /Users/gopher/go/pkg/darwin_amd64/code/utils.a
可以發現會把依賴包安裝到 GOPATH/pkg下,并且命名為.a結尾的歸檔文件。darwin_amd64是$GOOS_$GOARCH的拼裝,即操作系統和處理器架構,code就是我們的項目名。
/Users/gopher/go/pkg/darwin_amd64/code └── utils.a
所以go install大概流程是這樣的:
go get
通過go get 命令我們可以下載并安裝一個依賴包,go get下載的源碼文件會放在GOPATH中的第一個工作區。由于go在1.11開始支持go mod,所以當我們開啟go mod后,通過go get獲取的代碼會下載在GOPAT/pkg/mod中,go get后面支持可選參數
go mod
go從1.11開始支持go mod模式,現在相信大家也基本都在使用go mod,相比vendor的好處,使用go mod之后,你的代碼可以存在在任何位置。
go mod相關命令
//go mod download -json { "Path": "github.com/go-basic/uuid", "Version": "v1.0.0", "Info": "/Users/gopher/go/pkg/mod/cache/download/github.com/go-basic/uuid/@v/v1.0.0.info", "GoMod": "/Users/gopher/go/pkg/mod/cache/download/github.com/go-basic/uuid/@v/v1.0.0.mod", "Zip": "/Users/gopher/go/pkg/mod/cache/download/github.com/go-basic/uuid/@v/v1.0.0.zip", "Dir": "/Users/gopher/go/pkg/mod/github.com/go-basic/uuid@v1.0.0", "Sum": "h1:Faqtetcr8uwOzR2qp8RSpkahQiv4+BnJhrpuXPOo63M=", "GoModSum": "h1:yVtVnsXcmaLc9F4Zw7hTV7R0+vtuQw00mdXi+F6tqco=" }
編輯go.mod文件 選項有-json、-replace...,可以使用幫助go help mod edit,比如說如果你要修改某個包,可以直接使用 go mod edit -replace=old[@v]=new[@v],一般都是直接編輯go.mod文件了,這個命令用的不多。
以文本模式打印依賴的包,比如我的go.mod是
module code go 1.15 require ( github.com/gin-gonic/gin v1.7.4 // indirect github.com/go-basic/uuid v1.0.0 // indirect )
這時執行go mod graph
//go mod graph code github.com/gin-gonic/gin@v1.7.4 code github.com/go-basic/uuid@v1.0.0 github.com/gin-gonic/gin@v1.7.4 github.com/gin-contrib/sse@v0.1.0 github.com/gin-gonic/gin@v1.7.4 github.com/go-playground/validator/v10@v10.4.1 github.com/gin-gonic/gin@v1.7.4 github.com/golang/protobuf@v1.3.3 github.com/gin-gonic/gin@v1.7.4 github.com/json-iterator/go@v1.1.9 github.com/gin-gonic/gin@v1.7.4 github.com/mattn/go-isatty@v0.0.12 github.com/gin-gonic/gin@v1.7.4 ... golang.org/x/text@v0.3.2 golang.org/x/tools@v0.0.0-20180917221912-90fa682c2a6e
發現還是看不出依賴關系,這里推薦大家使用 go get -u github.com/PaulXu-cn/go-mod-graph-chart/gmchart這個包,來查看依賴關系:
添加丟失或移出不需要的模塊。當前我的go.mod里面有個uuid的包,但是我的代碼并沒有引用。
module code go 1.15 require github.com/go-basic/uuid v1.0.0 // indirect
執行go mod tidy:
module code go 1.15
會發現幫我移除了不需要的包。
6.go mod verify驗證依賴是否正確。7. go mod why解釋為什么需要包和模塊,比如執行: go mod why github.com/go-basic/uuid,然后輸出:
# github.com/go-basic/uuid code/utils github.com/go-basic/uuid
我的理解是 code/utils這個包有用到github.com/go-basic/uuid。
go.sum
go.sum文件的作用就兩個:
當我們go get某個包的時候,會先下載到本地$GOPATH/pkg/mod/cache/download中,下載下來后會有一個名為vx.x.x.zip的壓縮包,以及vx.x.x.ziphash的文件,vx.x.x.ziphash內容就是vx.x.x.zip經過hash的值,比如: h1:jwqTeEM3x6L9xDXrCxN0Hbg7vdGfPBOTIkr0+/LYZDA=% 以uuid包為例子:當我們go get github.com/go-basic/uuid后,除了會在go.mod里追加一條require命令后,還會在go.sum里面寫入兩條記錄:
github.com/go-basic/uuid v1.0.0 h1:Faqtetcr8uwOzR2qp8RSpkahQiv4+BnJhrpuXPOo63M= github.com/go-basic/uuid v1.0.0/go.mod h1:yVtVnsXcmaLc9F4Zw7hTV7R0+vtuQw00mdXi+F6tqco=
第一條hash就是我們上面提到的zip壓縮包的hash值,第二條hash是如果我們的依賴包中有go.mod,那么就是這條go.mod的hash值。在準備把兩條hash值記錄更新到go.sum中的時候,為了確保依賴包的真實可靠性,go在下載完依賴包后,會通過go的環境變量GOSUMDB="sum.golang.org"指向的服務器去檢查依賴包的hash值,如果查詢的hash值和本地的hash值不一樣,那么就拒絕向下執行,也不會更新go.sum。
go clean
//go clean -i -n cd /Users/gopher/go/job // 當前項目 rm -f job job.exe job.test job.test.exe main main.exe rm -f /Users/gopher/go/bin/job
先切到我們的項目中去,然后嘗試刪除當前目錄下的一些編譯文件如.exe、.test等,最后去嘗試刪除$GOPATH/bin/job這個因為go install產生的編譯文件。
// go clean -r -n cd /Users/gopher/go/job rm -f job job.exe job.test job.test.exe main main.exe cd /usr/local/go/src/fmt rm -f fmt.test fmt.test.exe cd /usr/local/go/src/errors rm -f errors.test errors.test.exe cd /usr/local/go/src/internal/reflectlite rm -f reflectlite.test reflectlite.test.exe ....
job項目依賴很多包,這些依賴的包也會執行刪除一些當前目錄的編譯文件。
go build的過程是產生一些緩存的,這些緩存是存在go的環境變量GOCACHE中的 -cache就是刪除相關的緩存的:
//go clean -n -cache rm -r /Users/gopher/Library/Caches/go-build/00 /Users/gopher/Library/Caches/go-build/01 /Users/gopher/Library/Caches/go-build/02 /Users/gopher/Library/Caches/go-build/03 /Users/gopher/Library/Caches/go-build/04 ... rm -r /Users/gopher/Library/Caches/go-build/ff
當我們使用go test .來跑某個路徑下面的測試用例時,會編譯并測試路徑下每個測試文件,并且會緩存測試結果,以避免不必要的重復測試,當緩存成功后,第二次跑test 會發現有個cached標識。
第一次 go test . ok job 0.431s 第二次 go test . ok job (cached)
這時候通過go clean -testcache 就是刪除對應的測試緩存。
go clean -testcache go test . ok job 0.459s
當我們啟動go.mod模式來組織我們的go代碼時,下載的依賴包會放在$GOPATH/pkg/mod中,通過 go clean -modcache就是刪除$GOPATH/pkg/mod下的所有文件。
//go clean -n -modcache rm -rf /Users/gopher/go/pkg/mod
結語
寫本文的目的主要是因為自己知識的匱乏,自己用go也兩年了,然而對go的一些命令、go的編譯過程還是存在模糊感的,比如項目一直使用的go.mod,go.mod是如何管理我們依賴的代碼包的,go.sum是什么?為什么需要go.sum?go build的過程發生了什么?另一方面本文也列舉了基本日常開發夠用的命令,這樣當自己需要查找命令不用去網上百度了,可以作為一個速查手冊。也許我們日常工作任務不太需要我們對底層知識的了解,但是保持好奇心也是一個程序員快速進步的一種方式。原理、底層這些很枯燥的東西,如果能啃下來,會發現很多問題都想通了。就好比練武功,武功高強的人,會發現他們的內功心法很強大,當內功心法強大了,學什么武功也就快了。
歡迎大家關注公眾號《假裝懂編程》,我將持續輸出網絡、數據庫、go、緩存、架構、面試、程序人生相關文章。
劉悅遠