Go lang使用包(package)這種概念元素來統籌程式碼,所有程式碼功能上的可呼叫性都定義在包這個級別,如果我們需要呼叫依賴,那就“導包”就行了,無論是內部的還是外部的,使用import關鍵字即可。但事情往往沒有那麼簡單,Go lang在包管理機制上走了不少彎路,雖然1.18版本的包管理已經趨於成熟,但前事不忘後事之師,我們還是需要了解一下這段歷史。
環境變數
一般情況下,go lang在系統中會依賴兩個環境變數,分別是:GOPATH 和 GOROOT,有點類似於Python的直譯器目錄的概念,GOROOT 這個變數的作用就是為了告訴當前執行的 Go 程式當前 Go安裝路徑,當要執行的時候去什麼位置找GoSDK相關的類。
GOPATH 這個變數的設定是預設所有的專案和引用的第三方包都下載到GOPATH的src目錄下,也就是說,你的程式碼只要不在GOPATH裡,就沒法編譯。我們可以理解這個目錄就是專案目錄,這一點跟Python區別還是挺大的,Python的pip包管理機制至今都是經典的包依賴設計模式。
GOPATH這種設定方式會導致很多問題,比方說我有很多go lang專案,而每個專案又都有自己的GOPATH目錄,那所有依賴的包就都在專案各自目錄下,導致重複包過多,反之,如果大家都用一個GOPATH目錄,又會帶來版本問題,每個專案依賴的包版本不一致,到底怎麼進行統籌又是一個問題,這就是GOPATH設定早期被人詬病的原因。
Go modules
針對因為GOPATH這種反人類的設計而導致的包管理亂象,Go lang 1.11 版本的時候推出了新特性 Go modules。
Go modules 是官方推出的依賴管理工具,Go modules 提供了3個重要的功能:
1.go.mod 檔案,它和Node的package.json檔案的功能相似,都是記錄當前專案的依賴關係。
2.機器生成的傳遞依賴項描述檔案 : go.sum。
3.不再有 GOPATH 的反人類限制,所有程式碼可以位於電腦的任何路徑中。
go lang1.18早已經整合了Go modules,但就像golang1.18第一篇精煉教程裡寫得那樣,預設還是反人類的GOPATH模式,你想用,得透過命令手動開啟:
go env -w GO111MODULE=on
為了能夠向下相容維護go1.11版本以下的專案,可以設定為相容模式:
go env -w GO111MODULE=auto
三方包管理
三方包指的是外部開源的一些包,而使用go modules機制管理三方包相對簡單,首先新建一個專案目錄,比如c:/www/test
cd c:/www/test
進入專案目錄後,初始化專案:
go mod init test
系統返回:
C:\Users\liuyue\www\test>go mod init test
go: creating new go.mod: module test
C:\Users\liuyue\www\test>dir
2022/08/12 12:13 <DIR> .
2022/08/12 12:13 <DIR> ..
2022/08/12 12:13 21 go.mod
1 個檔案 21 位元組
2 個目錄 228,767,113,216 可用位元組
這裡注意專案名和目錄名稱要吻合,go.mod 檔案是開啟 modules 的必備配置檔案。它記錄了當前專案引用的包資料資訊。go.mod 檔案中定義了以下關鍵詞:
module:用於定義當前專案的模組路徑
go:用於設定Go 版本資訊
require:用於設定一個特定的模組版本
exclude:用於從使用中排除一個特定的模組版本
replace:用於將一個模組版本替換為另一個模組版本
接著,執行go get命令安裝三方包,比如說gin框架:
go get github.com/gin-gonic/gin
隨後編寫main.go檔案,也就是main包:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
d := gin.Default()
d.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "hello go 1.18", "data": ""})
})
d.Run("127.0.0.1:5000")
}
這裡將剛剛安裝的三方包匯入,然後再main函式中呼叫。
緊接著啟動服務:
go run main.go
系統返回:
C:\Users\liuyue\www\test>go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET / --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on 127.0.0.1:5000
[GIN] 2022/08/12 - 12:19:20 |[97;42m 200 [0m| 3.8876ms | 127.0.0.1 |[97;44m GET [0m "/"
[GIN] 2022/08/12 - 12:19:21 |[90;43m 404 [0m| 0s | 127.0.0.1 |[97;44m GET [0m "/favicon.ico"
說明三方包的服務已經啟動了,訪問http://localhost:5000:
這就是一個go lang專案匯入三方包的具體流程。
接著我們開啟專案中的go.mod檔案:
module test
go 1.18
require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.8.1 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
三方包gin以及gin所依賴的三方包都一目瞭然。
內部包管理
內部包指的是專案內部的包,一般情況下就是自己開發的可複用的包,go modules也可以對內部包進行管理,在剛剛建立的test專案中,新建目錄my:
C:\Users\liuyue\www\test>mkdir my
C:\Users\liuyue\www\test>dir
驅動器 C 中的卷沒有標籤。
卷的序列號是 0A64-32BF
C:\Users\liuyue\www\test 的目錄
2022/08/12 12:39 <DIR> .
2022/08/12 12:13 <DIR> ..
2022/08/12 12:18 1,046 go.mod
2022/08/12 12:18 6,962 go.sum
2022/08/12 12:16 228 main.go
2022/08/12 12:39 <DIR> my
3 個檔案 8,236 位元組
3 個目錄 228,568,178,688 可用位元組
C:\Users\liuyue\www\test>
然後再my目錄新建一個my.go檔案:
package my
import "fmt"
func New() {
fmt.Println("我是my包")
}
這裡我們宣告包與目錄名一致,隨後再宣告一個New函式。
接著改寫main.go內容:
package main
import (
"fmt"
"test/my"
)
func main() {
fmt.Println("main run")
// 使用my
my.New()
}
程式返回:
main run
我是my包
觸類旁通,如果包不在同一個專案下:
├── moduledemo
│ ├── go.mod
│ └── main.go
└── mypackage
├── go.mod
└── mypackage.go
這個時候,mypackage也需要進行module初始化,即擁有一個屬於自己的go.mod檔案,內容如下:
module mypackage
go 1.18
然後我們在moduledemo/main.go中按如下方式匯入:
import (
"fmt"
"mypackage"
)
func main() {
mypackage.New()
fmt.Println("main")
}
結語
對於 Go lang 的專案來說,如果沒有開啟 go mod模式,那麼專案就必須放在 GOPATH/src 目錄下,專案本身也可以看作為一個本地包,可以被其它 GOPATH/src目錄下的專案引用,同時也可以被go modules模式的專案引入,因為go modules的原理是先去GOPATH/src目錄下定址,如果沒有才去指定目錄定址,但反過來,如果是放在go modules專案中的本地包,GOPATH/src目錄下的專案就無法引用,因為GOPATH規定專案都必須得放在GOPATH/src目錄下,它只會在GOPATH/src目錄下定址,這是我們需要注意的地方。