Golang
是一門到現在有十年的靜態高級語言了,2009年的時候算是正式推出了,而後到最近的一兩年,2017-2018年的時候,忽然直線上升,爆火了,得益於容器化運維/直播/短視頻/區塊鏈...git
Golang
語法簡單,簡單便是複雜,軟件構建的核心在於將複雜的東西簡單化,處理好複雜度。github
做爲一個 gopher
,咱們要知道他的包管理,這樣才能合理化代碼結構,作好工程管理。(gopher
:地鼠)golang
Golang
的包管理一直讓人口病,一開始它用 GOPATH
來進行依賴庫管理,特別簡單粗暴。docker
若是環境變量:npm
export GOROOT=/home/love/go export GOPATH=/home/love/code export GOBIN=$GOROOT/bin
上面 GOROOT
是指 Golang編譯器以及其工具鏈,基礎源碼庫
所在的目錄, GOPATH
是用戶自定義的代碼所在位置。json
如下 GOPATH
的結構以下:緩存
├── src └── github.com └── hunterhug └── rabbit └── a └── a.go └── main.go ├── bin ├── pkg
咱們寫的開發包有簡單易懂的路徑之分,好比個人包叫 github/hunterhug/rabbit
,那麼結構如上面同樣。cors
咱們進入到 rabbit
目錄,main.go
代碼:運維
package main import "github/hunterhug/rabbit/a" func main(){ ... }
而後 go build
的話,找包時,就會從 GOPATH src
下面開始找,好比 rabbit
包下的 main.go
依賴了 github/hunterhug/rabbit/a
,那麼它首先從 src
下面按路徑往下拼接查找,而後就找到了,最後生成和包名 github/hunterhug/rabbit
同樣的一個叫 rabit
的二進制。maven
若是咱們 go install
的話,這個二進制就會保存在 GOBIN
下(若是不存在 GOBIN
,會保存在 GOPATH bin
下)。若是咱們要編譯時,缺乏包,那麼 go get -v
將會下載依賴包源碼到 GOPATH src
下,而後在 GOPATH pkg
目錄下生成該包的靜態庫(下次用就不用再從源碼編譯了,算緩存)。
可是咱們包找不到時:
love@love:~/code/src/github.com/hunterhug/fafacms$ go build core/server/server.go:4:2: cannot find package "github.com/gin-contrib/cors" in any of: /home/love/go/src/github.com/gin-contrib/cors (from $GOROOT) /home/love/code/src/github.com/gin-contrib/cors (from $GOPATH)
咱們發現,原來,實際上是先去 GOROOT
下找包,找不到包,再去 GOPATH
找,流下了感動的淚水!好比咱們的 GOPATH
下建了一個 fmt
包:
package fmt func PPrintln() { print("i am diy fmt") }
可是咱們想引用這個庫,main.go
使用:
package main import fmt func main(){ fmt.PPrintln() }
發現引用不了,2333! 因此,GOPATH
下的包最好不要和 GOROOT
下的標準庫重名!
你再看下 GOROOT
的結構:
├── src └── time └── fmt ├── bin ├── pkg
這不和咱們的 GOPATH
很像嗎,對,如今的 Golang編譯器
是自編譯的,就是用 Golang
來寫 Golang編譯器
,它的編譯器及中間產物,基礎庫等,保持和 GOPATH
一毛同樣,無縫銜接。
可是不一樣依賴包是有版本的,版本變了怎麼辦?這就須要人工管理了。
本身管理庫版本,想一想都不太可能,畢竟 Java
有 maven
, Python
有 pip
, PHP
有 compose
,NodeJs
有 npm
。
因而從 Golang1.5
開始推出 vendor
文件夾機制( vendor
:供應商/小販)。
從 Golang1.6
正式開啓這個功能。
好比咱們的包叫 awesomeProject
,在 GOPATH
下結構:
├── src └── awesomeProject └── vendor └── fmt └── fmt.go └── main.go ├── pkg
其中 main.go
:
package main import "fmt" func main() { fmt.PPrintln() }
咱們進入 awesomeProject
目錄,而且 go build
, 偶也成功。
這下子不會像上面沒 vendor
時直接引用 GOROOT
的標準包了,咱們終於能夠用和標準包重名的包了,那就是放在和 main.go
同目錄的 vendor
下面!
這下子,咱們 import
的包會先在同級 vendor
下找,找不到再按照之前的方式。
若是咱們將 main
改爲引用一個不存在的包 b
:
package main import ( "b" ) func main() { b.P() }
而後 go build
提示:
main.go:4:2: cannot find package "b" in any of: /home/love/code/src/awesomeProject/vendor/b (vendor tree) /home/love/go/src/b (from $GOROOT) /home/love/code/src/b (from $GOPATH)
若是此時咱們再任性一點,在 GOPATH src
下創建一個空的 vendor
文件夾,則會提示:
main.go:4:2: cannot find package "b" in any of: /home/love/code/src/awesomeProject/vendor/b (vendor tree) /home/love/code/src/vendor/b /home/love/go/src/b (from $GOROOT) /home/love/code/src/b (from $GOPATH)
好了,咱們發現如今的加載方式是:
包同目錄下的vendor GOPATH src 下的vendor GOROOT src GOPATH src
若是在 GOROOT
和 GOPATH
下建 vendor
會怎麼樣?咱們就不止疼了,233。。
好了,如今問題就是 vendor
是怎麼冒泡的,若是我 main.go
引用了 vendor/b
,而 b
包裏面引用了一個 c
包。此時 vendor/b
會怎麼找庫?
├── src └── awesomeProject └── vendor └── b └── b.go └── main.go ├── pkg
如今 vendor/b/b.go
的內容:
package b import "c" func P() { print(" i am vendor b\n") c.P() }
咱們進入 awesomeProject
項目 go build
,出現:
vendor/b/b.go:3:8: cannot find package "c" in any of: /home/love/code/src/awesomeProject/vendor/c (vendor tree) /home/love/code/src/vendor/c /home/love/go/src/c (from $GOROOT) /home/love/code/src/c (from $GOPATH)
如今加載流程是:
包同目錄的包(即b包同目錄看看有沒有c包) GOPATH src 下的vendor GOROOT src GOPATH src
此時咱們在 vendor/b
下建一個空 vendor
:
├── src └── awesomeProject └── vendor └── b └── vendor └── b.go └── main.go ├── pkg
進入 awesomeProject
項目再 go build
會出現:
vendor/b/b.go:3:8: cannot find package "c" in any of: /home/love/code/src/awesomeProject/vendor/b/vendor/c (vendor tree) /home/love/code/src/awesomeProject/vendor/c /home/love/code/src/vendor/c /home/love/go/src/c (from $GOROOT) /home/love/code/src/c (from $GOPATH)
若是咱們再知足上面的 c
包,同理在 c
包建一個空 vendor
:
├── src └── awesomeProject └── vendor └── b └── vendor └── c └── vendor └── c.go └── b.go └── main.go ├── pkg
但 c
包 c.go
引用了不存在的 d
包:
package c import "d" func P() { d.P() }
進入 awesomeProject
項目再 go build
會出現:
vendor/b/vendor/c/c.go:3:8: cannot find package "d" in any of: /home/love/code/src/awesomeProject/vendor/b/vendor/c/vendor/d (vendor tree) /home/love/code/src/awesomeProject/vendor/b/vendor/d /home/love/code/src/awesomeProject/vendor/d /home/love/code/src/vendor/d /home/love/go/src/d (from $GOROOT) /home/love/code/src/d (from $GOPATH)
發現, 查找包 vendor
是往上冒泡的, 一個包引用另外一個包,先看看 同目錄 vendor
下有沒有這個包, 沒有的話一直追溯到上一層 vendor
看有沒有,沒有的話再上一層 vendor
,直到 GOPATH src/vendor
。
因此如今的加載流程是:
包同目錄下的vendor 包目錄向上的最近的一個vendor ... GOPATH src 下的vendor GOROOT src GOPATH src
總結: vendor
向上冒泡!!!!
這樣的話, 咱們能夠把包的依賴都放在 vendor
下,而後提交到倉庫,這樣能夠省卻拉取包的時間,而且相對自由,你想怎麼改均可以,你能夠放一個已經被人刪掉的 github
包在 vendor
下。這樣,依然手動,無法管理依賴版本。
因此不少第三方,好比 glide
, godep
, govendor
工具出現了, 使用這些工具, 依賴包必須有完整的 git
版本, 而後會將全部依賴的版本寫在一個配置文件中。
好比 godep
:
go get -v github.com/tools/godep
在包下執行
godep save
會生成 Godeps/Godep.json
記錄依賴版本,而且將包收集於 當前vendor
下。
Golang 1.11
開始, 實驗性出現了能夠不用定義 GOPATH
的功能,且官方有 go mod
支持。Golang 1.12
更是將此特徵正式化。
如今用 Golang1.12
進行:
go mod init go: modules disabled inside GOPATH/src by GO111MODULE=auto; see 'go help modules'
其中 GO111MODULE=auto
是一個開關,開啓或關閉模塊支持,它有三個可選值: off
/on
/auto
,默認值是 auto
。
GO111MODULE=off
,無模塊支持,和以前同樣。GO111MODULE=on
,模塊支持,忽略 GOPATH
和 vendor
文件夾,只根據 go.mod
下載依賴。GO111MODULE=auto
,該項目在 GOPATH src
外面且根目錄有 go.mod
文件時,開啓模塊支持。在使用模塊的時候, GOPATH
是無心義的,不過它仍是會把下載的依賴儲存在 GOPATH/src/mod
中,也會把 go install
的結果放在 GOPATH/bin
(若是 GOBIN
不存在的話)
咱們將項目移出 GOPATH
,而後:
go mod init
出現:
go: cannot determine module path for source directory /home/love/awesomeProject (outside GOPATH, no import comments)
如今 main.go
改成:
package main // import "github.com/hunterhug/hello" import ( "b" ) func main() { b.P() }
將會生成 go.mod
:
module github.com/hunterhug/hello go 1.12
此時咱們:
go build build github.com/hunterhug/hello: cannot load b: cannot find module providing package b
這下無法查找 vendor
了,咱們加上參數再來:
go build -mod=vendor build github.com/hunterhug/hello: cannot load c: open /home/love/awesomeProject/vendor/c: no such file or directory
流下了感動的淚水, vendor
冒泡呢?原來啓用了 go.mod
,vendor
下的包 b
沒法找到b/vendor
下的包 c
,只能找到一級,2333333,這是好仍是壞?
通常狀況下, vendor
下面有 vendor
是不科學的, godep
等工具會將依賴理順,確保只有一個 vendor
。
那麼 go.mod
致使 vendor
沒法冒泡產生的影響,一點都不大,流下感動的淚水。
如今咱們來正確使用 go mod
, 通常狀況下:
省略N步
到了這裏,咱們很遺憾的說再見了,如今 go mod
剛出來, 可能還會再更新,您能夠谷歌或者其餘方式搜索這方面的文章,或者:
go help modules
這一部分可能隔一段時間再細寫。
目前生產環境用 go mod
還不太現實, 我仍是先推薦定義 GOPATH
和 vendor
用法。
裝環境太難, 個人天啊, 我每次都要裝環境, 咱們能夠用下面的方法 So easy
隨時切換 Golang
版本。
若是你的 Golang
項目依賴存於 vendor
下,那麼咱們可使用多階段構建並打包成容器鏡像,Dockefile
以下:
FROM golang:1.12-alpine AS go-build WORKDIR /go/src/github.com/hunterhug/fafacms COPY core /go/src/github.com/hunterhug/fafacms/core COPY vendor /go/src/github.com/hunterhug/fafacms/vendor COPY main.go /go/src/github.com/hunterhug/fafacms/main.go RUN go build -ldflags "-s -w" -o fafacms main.go FROM alpine:3.9 AS prod WORKDIR /root/ COPY --from=go-build /go/src/github.com/hunterhug/fafacms/fafacms /bin/fafacms RUN chmod 777 /bin/fafacms CMD /bin/fafacms $RUN_OPTS
其中 github.com/hunterhug/fafacms
是你的項目。使用 golang:1.12-alpine
來編譯二進制,而後將二進制打入基礎鏡像:alpine:3.9
,這個鏡像特別小。
編譯:
sudo docker build -t hunterhug/fafacms:latest .
咱們多了一個鏡像 hunterhug/fafacms:latest
, 並且特別小, 才幾M 。
運行:
sudo docker run -d --net=host --env RUN_OPTS="-config=/root/fafacms/config.json" hunterhug/fafacms
但是,若是咱們用了 cgo
, 那麼請將 Dockerfile
改成:
FROM golang:1.12 AS go-build WORKDIR /go/src/github.com/hunterhug/fafacms COPY core /go/src/github.com/hunterhug/fafacms/core COPY vendor /go/src/github.com/hunterhug/fafacms/vendor COPY main.go /go/src/github.com/hunterhug/fafacms/main.go RUN go build -ldflags "-s -w" -o fafacms main.go FROM bitnami/minideb-extras-base:stretch-r165 AS prod WORKDIR /root/ COPY --from=go-build /go/src/github.com/hunterhug/fafacms/fafacms /bin/fafacms RUN chmod 777 /bin/fafacms CMD /bin/fafacms $RUN_OPTS
管理依賴,到如何將代碼編譯成二進制,是一個過程,還有許多細節。 上面是個人經驗,感謝閱讀。