golang的 GOPATH和vendor的搜尋關係
golang的 GOPATH和vendor的搜尋關係
基本規則
-
- 所有的go檔案都是必須組織成包的形式,放在相應資料夾下:
- 1.1 建議包名和資料夾名字相同;雖然也可以不同,但會引發使用誤解。
-
1.2 對於主程式包,也需要放在資料夾下面,注意:
- 1.2.1 不建議使用main作為資料夾名,雖然這個包名是main。
- 1.2.2 也不建議使用src作為檔名,儘管這是允許的,但是會引發誤解。
- 1.2.3 建議使用專案名字作為包名。
-
- go build命令如果不帶引數,就是build當前包,當前目錄所在的包,即當前目錄下面的所有go檔案。
- 1.2 如果go build指定了目標包,那麼就會從GOPATH路徑下面搜尋包,如果找不到,就報失敗;哪怕當前路徑就在目標包裡,但是GOPATH沒有包含,也會報失敗。
- 1.2 如果GOPATH沒有設定,其預設路徑就是$HOME/gp
例子1:完全自包含專案
專案只有一個包,即main包,沒有引用其他的包(golang自帶的系統包除外)。
-
1.新建資料夾,例如myproject。
mkdir myproject
-
- 編輯專案檔案
[~/myproject]$ cat main.go package main import "fmt" func main() { fmt.Printf("main::main\n"); foo() } [~/myproject]$ cat foo.go package main import "fmt" func foo() { fmt.Printf("main::foo\n"); }
-
- 編譯專案
[~/myproject]$ unset GOPATH [~/myproject]$ go build [~/myproject]$ ls -1 foo.go main.go myproject
- 直接進入專案目錄執行 go build,即編譯當前包。
- 不需要設定GOPATH值,預設就是~/go,因為這是一個自包含專案,不需要引用GOPATH的任何值。
- 編譯生成的可執行檔名就是專案資料夾名。
- 注意當前目錄必須是專案檔案所在目錄,因為go build沒有指定目標包,預設編譯當前目錄包;如果不是就不行,那得必須按照golang的專案組織規範來組織。
<goproject> |-- src |-- myproject |-- main.go |-- foo.go
然後設定GOPATH=path/to/<goproject>,再執行go build myproject,這樣就可以在任何目錄下面編譯,編譯生成的可執行檔案就在編譯所在的目錄下,而不是包原始檔所在的目錄。
例子2:引用了其他的包
基本規則:
-
1.
import <package>
總是從$GOPATH/src目錄下面搜尋包,如果找不到就報錯。- 1.2 並不會從當前目錄下面去搜索,也不會從原始檔相對目錄下面去搜索。
- 1.GOPATH可以包含多個路徑,中間用冒號(:)隔開,就像PATH一樣。
鑑於此,建議golang專案必須嚴格按照規範的目錄結構組織,哪怕是前面這種自包含的專案。
例子3:vendor目錄的使用
基本規則:
-
1.使用vendor,專案必須嚴格按照規範的目錄結構組織。
- 1.2 即使像例子1中自包含的專案也不能使用vendor
- 2.vender需要在原檔案下面建立vendor目錄,然後把vendor的檔案包放入vendor目錄即可,在引用的時候不需要指定vendor路徑。
[~/]$ find <goproject> <goproject> <goproject>/src <goproject>/src/myproject <goproject>/src/myproject/main.go <goproject>/src/myproject/vendor <goproject>/src/myproject/vendor/mydeps <goproject>/src/myproject/vendor/mydeps/dep1.go [~/<goproject>]$ cat <goproject>/src/myproject/main.go package main import "fmt" import "mydeps" func main() { fmt.Printf("main::main\n"); mydeps.Foo() } [~/<goproject>]$ cat <goproject>/src/myproject/vendor/mydeps/dep1.go package mydeps import "fmt" func Foo() { fmt.Println("in mydeps::Foo") }
例子4:vendor和GOPATH誰優先使用
如果一個包在vendor和GOPATH下面都存在那麼誰會優先使用呢。
結論是:
-
- 優先使用vendor目錄下面的包。
-
- 如果vendor下面沒有搜尋到,再搜尋GOPATH下面的包。
-
-
要麼完整使用vendor下面的包,要麼完整使用GOPATH下面的包,不會混合使用:
-3.1 假如一個函式定義再GOPATH下面的包裡,而沒有定義在vendor路徑下的同名包裡,那麼呼叫者就會報函式未定義錯誤,因為呼叫者如果找到有vendor路徑下面的包,就不會去找GOPATH下面的包了。
-
[~/<goproject>]$ find src src src/myproject src/myproject/main.go src/myproject/vendor src/myproject/vendor/mydeps src/myproject/vendor/mydeps/dep1.go src/mydeps src/mydeps/dep1.go
包mydeps在vendor目錄下面和GOPATH路徑下面都存在了,那麼main.go引用的時候只會引用vendor下面的mydeps(src/myproject/vendor/mydeps),而忽略GOPATH下面的mydeps包(src/mydeps)。
例子5:vendor的層級搜尋
前面提到GOPATH和PATH類似,可以包含多個路徑,中間用分號隔開,go在搜尋包的時候會按手續從前往後搜搜。那麼vendor怎麼處理層級關係呢。
規則是:
- 1.從引用檔案所在的vendor路徑下面搜尋。
- 2.如果沒有找到,那麼從上層目錄的vendor路徑下面搜尋。
- 3.直到src的vendor路徑下面搜尋。
舉例:
[~/<goproject>]$ find ./ ./src ./src/myproject ./src/myproject/myproject ./src/myproject/main.go ./src/mydep ./src/mydep/mydep1 ./src/mydep/mydep1/mydep.go ./src/mydep/mydep1/vendor ./src/mydep/mydep1/vendor/myvendor1 ./src/mydep/mydep1/vendor/myvendor1/myvendor.go ./src/mydep/mydep.go ./src/mydep/vendor ./src/mydep/vendor/myvendor ./src/mydep/vendor/myvendor/myvendor.go
如果src/mydep/mydep1/mydep.go引用了myvendor1和myvendor,那是怎麼搜尋的呢
-
先從src/mydep/mydep1/vendor下面搜尋myvendor1。
找到了,直接使用。 -
先從src/mydep/mydep1/vendor下面搜尋myvendor。
發現沒有找到,那麼從上層路徑搜尋,即: -
先從src/mydep/vendor下面搜尋myvendor。
找到了,直接使用。 -
如果還沒有找到,那麼繼續向上一級搜尋,即
src/vendor - 如果找到了,則使用;如果還沒有找到,那麼繼續從GOPATH裡搜尋,直到找到或者失敗。
總結
- 建議golang專案嚴格按照golang專案組織方式,即使只是一個自包含的專案。
<goproject> |-- src |-- mainpackage |-- XXX.go |-- YYY.go |-- vendor |-- deppackage1 |-- XXX1.go |-- YYY1.go |-- vendor |-- deppackage2 |-- XXX2.go |-- YYY2.go |-- vendor |-- VVV1.go |-- VVV2.go |-- vendor
-
GOPATH使用分號(:)隔開的多個路徑。
go編譯的時候會從GOPATH/src目錄下面搜尋import的包。 -
vender目錄放在原始檔目錄同級,下面包含各個包。
3.1 vendor的搜尋優先於GOPATH的搜尋。
3.2 vendor按照路徑深度向外按順序搜尋,直到$GOPATH/src/vendor為止。