科技改變生活 · 科技引領(lǐng)未來
寫在前面go語言開發(fā)者在日常工作或?qū)W習(xí)中,用到最多的命令可能是gobuild、gorun、goinstall、goget...,通過這些命令可以幫我們編譯程序、運(yùn)行程序、安裝程序和獲取代碼包,然而在執(zhí)行這些命令的時(shí)候,你是否思考過go編譯過
寫在前面
go語言開發(fā)者在日常工作或?qū)W習(xí)中,用到最多的命令可能是go build、go run、go install、go get...,通過這些命令可以幫我們編譯程序、運(yùn)行程序、安裝程序和獲取代碼包,然而在執(zhí)行這些命令的時(shí)候,你是否思考過go編譯過程中是如何組織我們的源碼文件的?go install的時(shí)候發(fā)生了什么?以及go get只是去下載我們依賴的包文件嗎?go還有哪些實(shí)用的命令?這是一篇可以說比較基礎(chǔ)的文章,也可以作為go命令的一個(gè)速查手冊,旨在幫助我們更深刻的了解go。
幾個(gè)概念
gopath可能是每個(gè)go開發(fā)者最熟悉不過的東西了,gopath就是我們的工作區(qū),通過go env可以查看到我們的gopath,如 GOPATH="/Users/code/go",gopath就是我們的工作區(qū),gopath下面一般會(huì)建立bin、src、pkg三個(gè)文件夾。
export GOBIN=/tmp //聲明GOBIN目錄 go install $GOPATH/src/code //安裝code程序 /tmp/code 發(fā)現(xiàn)程序已經(jīng)被安裝到了GOBIN下
常見輔助命令
go有一些輔助命令可以幫助我們更好的理解go的一些執(zhí)行過程,輔助命令的體現(xiàn)就是go run/build.. -x xx.go, -x就是輔助命令。-x可以有以下類型:
小眾輔助命令
編譯
go run
我們通過go run -n main.go來看下構(gòu)建的過程
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,我們來看看臨時(shí)文件夾:
go run -work main.go WORK=/var/folders/s4/2cpbmp4s1_j4y3zv2s08m9q40000gn/T/go-build281107053
切到臨時(shí)目錄:
└── b001 ├── exe │ └── main └── importcfg.link
默認(rèn)情況下,go run命令運(yùn)行完后會(huì)刪除臨時(shí)文件夾。
go build
go build用于編譯我們的程序,默認(rèn)編譯后的文件存放在當(dāng)前的文件夾中,如果指定-o那么就可以移動(dòng)到指定的文件中。我們通過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之后,生成的可執(zhí)行文件會(huì)移動(dòng)到當(dāng)前文件夾中,并不是隨著臨時(shí)文件夾一起消亡。
go install
go install用于編譯并安裝指定的代碼包及它們的依賴包,當(dāng)指定的代碼包的依賴包還沒編譯安裝的時(shí)候,會(huì)先去安裝依賴包。與go build不同的是,go install會(huì)把編譯后的安裝包放在指定的文件夾中。安裝的代碼包會(huì)在當(dāng)前工作區(qū)的pkg目錄下,即.a的歸檔文件,當(dāng)我們沒有設(shè)置GOBIN時(shí),安裝的命令源碼文件會(huì)存放在當(dāng)前工作區(qū)的bin目錄下,當(dāng)我們設(shè)置了GOBIN時(shí),則會(huì)放在GOBIN下。 假設(shè)現(xiàn)在項(xiàng)目是這樣的:
├── go.mod ├── main.go └── utils └── utils.go
main.go就是我們的入口文件,即命令源碼文件,utils是我們的依賴包,即庫源碼文件。 當(dāng)我們只在當(dāng)前目錄執(zhí)行g(shù)o 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
可以發(fā)現(xiàn)最后一行與go build的不同的是,它把可執(zhí)行文件移動(dòng)到了GOPATH/bin中。當(dāng)我們切到依賴包utils文件中執(zhí)行 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
可以發(fā)現(xiàn)會(huì)把依賴包安裝到 GOPATH/pkg下,并且命名為.a結(jié)尾的歸檔文件。darwin_amd64是$GOOS_$GOARCH的拼裝,即操作系統(tǒng)和處理器架構(gòu),code就是我們的項(xiàng)目名。
/Users/gopher/go/pkg/darwin_amd64/code └── utils.a
所以go install大概流程是這樣的:
go get
通過go get 命令我們可以下載并安裝一個(gè)依賴包,go get下載的源碼文件會(huì)放在GOPATH中的第一個(gè)工作區(qū)。由于go在1.11開始支持go mod,所以當(dāng)我們開啟go mod后,通過go get獲取的代碼會(huì)下載在GOPAT/pkg/mod中,go get后面支持可選參數(shù)
go mod
go從1.11開始支持go mod模式,現(xiàn)在相信大家也基本都在使用go mod,相比vendor的好處,使用go mod之后,你的代碼可以存在在任何位置。
go mod相關(guān)命令
//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文件 選項(xiàng)有-json、-replace...,可以使用幫助go help mod edit,比如說如果你要修改某個(gè)包,可以直接使用 go mod edit -replace=old[@v]=new[@v],一般都是直接編輯go.mod文件了,這個(gè)命令用的不多。
以文本模式打印依賴的包,比如我的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 )
這時(shí)執(zhí)行g(shù)o 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
發(fā)現(xiàn)還是看不出依賴關(guān)系,這里推薦大家使用 go get -u github.com/PaulXu-cn/go-mod-graph-chart/gmchart這個(gè)包,來查看依賴關(guān)系:
添加丟失或移出不需要的模塊。當(dāng)前我的go.mod里面有個(gè)uuid的包,但是我的代碼并沒有引用。
module code go 1.15 require github.com/go-basic/uuid v1.0.0 // indirect
執(zhí)行g(shù)o mod tidy:
module code go 1.15
會(huì)發(fā)現(xiàn)幫我移除了不需要的包。
6.go mod verify驗(yàn)證依賴是否正確。7. go mod why解釋為什么需要包和模塊,比如執(zhí)行: go mod why github.com/go-basic/uuid,然后輸出:
# github.com/go-basic/uuid code/utils github.com/go-basic/uuid
我的理解是 code/utils這個(gè)包有用到github.com/go-basic/uuid。
go.sum
go.sum文件的作用就兩個(gè):
當(dāng)我們go get某個(gè)包的時(shí)候,會(huì)先下載到本地$GOPATH/pkg/mod/cache/download中,下載下來后會(huì)有一個(gè)名為vx.x.x.zip的壓縮包,以及vx.x.x.ziphash的文件,vx.x.x.ziphash內(nèi)容就是vx.x.x.zip經(jīng)過hash的值,比如: h1:jwqTeEM3x6L9xDXrCxN0Hbg7vdGfPBOTIkr0+/LYZDA=% 以u(píng)uid包為例子:當(dāng)我們go get github.com/go-basic/uuid后,除了會(huì)在go.mod里追加一條require命令后,還會(huì)在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是如果我們的依賴包中有g(shù)o.mod,那么就是這條go.mod的hash值。在準(zhǔn)備把兩條hash值記錄更新到go.sum中的時(shí)候,為了確保依賴包的真實(shí)可靠性,go在下載完依賴包后,會(huì)通過go的環(huán)境變量GOSUMDB="sum.golang.org"指向的服務(wù)器去檢查依賴包的hash值,如果查詢的hash值和本地的hash值不一樣,那么就拒絕向下執(zhí)行,也不會(huì)更新go.sum。
go clean
//go clean -i -n cd /Users/gopher/go/job // 當(dāng)前項(xiàng)目 rm -f job job.exe job.test job.test.exe main main.exe rm -f /Users/gopher/go/bin/job
先切到我們的項(xiàng)目中去,然后嘗試刪除當(dāng)前目錄下的一些編譯文件如.exe、.test等,最后去嘗試刪除$GOPATH/bin/job這個(gè)因?yàn)間o install產(chǎn)生的編譯文件。
// 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項(xiàng)目依賴很多包,這些依賴的包也會(huì)執(zhí)行刪除一些當(dāng)前目錄的編譯文件。
go build的過程是產(chǎn)生一些緩存的,這些緩存是存在go的環(huán)境變量GOCACHE中的 -cache就是刪除相關(guān)的緩存的:
//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
當(dāng)我們使用go test .來跑某個(gè)路徑下面的測試用例時(shí),會(huì)編譯并測試路徑下每個(gè)測試文件,并且會(huì)緩存測試結(jié)果,以避免不必要的重復(fù)測試,當(dāng)緩存成功后,第二次跑test 會(huì)發(fā)現(xiàn)有個(gè)cached標(biāo)識(shí)。
第一次 go test . ok job 0.431s 第二次 go test . ok job (cached)
這時(shí)候通過go clean -testcache 就是刪除對應(yīng)的測試緩存。
go clean -testcache go test . ok job 0.459s
當(dāng)我們啟動(dòng)go.mod模式來組織我們的go代碼時(shí),下載的依賴包會(huì)放在$GOPATH/pkg/mod中,通過 go clean -modcache就是刪除$GOPATH/pkg/mod下的所有文件。
//go clean -n -modcache rm -rf /Users/gopher/go/pkg/mod
結(jié)語
寫本文的目的主要是因?yàn)樽约褐R(shí)的匱乏,自己用go也兩年了,然而對go的一些命令、go的編譯過程還是存在模糊感的,比如項(xiàng)目一直使用的go.mod,go.mod是如何管理我們依賴的代碼包的,go.sum是什么?為什么需要go.sum?go build的過程發(fā)生了什么?另一方面本文也列舉了基本日常開發(fā)夠用的命令,這樣當(dāng)自己需要查找命令不用去網(wǎng)上百度了,可以作為一個(gè)速查手冊。也許我們?nèi)粘9ぷ魅蝿?wù)不太需要我們對底層知識(shí)的了解,但是保持好奇心也是一個(gè)程序員快速進(jìn)步的一種方式。原理、底層這些很枯燥的東西,如果能啃下來,會(huì)發(fā)現(xiàn)很多問題都想通了。就好比練武功,武功高強(qiáng)的人,會(huì)發(fā)現(xiàn)他們的內(nèi)功心法很強(qiáng)大,當(dāng)內(nèi)功心法強(qiáng)大了,學(xué)什么武功也就快了。
歡迎大家關(guān)注公眾號(hào)《假裝懂編程》,我將持續(xù)輸出網(wǎng)絡(luò)、數(shù)據(jù)庫、go、緩存、架構(gòu)、面試、程序人生相關(guān)文章。
劉悅遠(yuǎn)
版權(quán)所有 未經(jīng)許可不得轉(zhuǎn)載
增值電信業(yè)務(wù)經(jīng)營許可證備案號(hào):遼ICP備14006349號(hào)
網(wǎng)站介紹 商務(wù)合作 免責(zé)聲明 - html - txt - xml