Golang1.5到Golang1.12包管理:golang vendor 到 go mod

1. 前言

Golang 是一門到現在有十年的靜態高級語言了,2009年的時候算是正式推出了,而後到最近的一兩年,2017-2018年的時候,忽然直線上升,爆火了,得益於容器化運維/直播/短視頻/區塊鏈...git

Golang 語法簡單,簡單便是複雜,軟件構建的核心在於將複雜的東西簡單化,處理好複雜度。github

做爲一個 gopher,咱們要知道他的包管理,這樣才能合理化代碼結構,作好工程管理。(gopher:地鼠)golang

2. GOPATH/ Golang 1.5以前

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 一毛同樣,無縫銜接。

可是不一樣依賴包是有版本的,版本變了怎麼辦?這就須要人工管理了。

3. Golang vendor/Golang1.5之後

本身管理庫版本,想一想都不太可能,畢竟 Javamaven, Pythonpip, PHPcomposeNodeJsnpm

因而從 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

若是在 GOROOTGOPATH 下建 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

cc.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下。

3. go mod/Go1.11之後

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

  1. GO111MODULE=off,無模塊支持,和以前同樣。
  2. GO111MODULE=on,模塊支持,忽略 GOPATHvendor 文件夾,只根據 go.mod下載依賴。
  3. 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.modvendor 下的包 b 沒法找到b/vendor 下的包 c,只能找到一級,2333333,這是好仍是壞?

通常狀況下, vendor 下面有 vendor 是不科學的, godep 等工具會將依賴理順,確保只有一個 vendor

那麼 go.mod 致使 vendor 沒法冒泡產生的影響,一點都不大,流下感動的淚水。

如今咱們來正確使用 go mod, 通常狀況下:

省略N步

到了這裏,咱們很遺憾的說再見了,如今 go mod 剛出來, 可能還會再更新,您能夠谷歌或者其餘方式搜索這方面的文章,或者:

go help modules

這一部分可能隔一段時間再細寫。

目前生產環境用 go mod 還不太現實, 我仍是先推薦定義 GOPATHvendor 用法。

4. 使用 Docker 來多階段編譯 Golang

裝環境太難, 個人天啊, 我每次都要裝環境, 咱們能夠用下面的方法 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

5. 總結

管理依賴,到如何將代碼編譯成二進制,是一個過程,還有許多細節。 上面是個人經驗,感謝閱讀。

相關文章
相關標籤/搜索