層次分明井然有條,Go lang1.18入門精煉教程,由白丁入鴻儒,Go lang包管理機制(package)EP10

劉悅的技術部落格 發表於 2022-08-13
Go

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 lang1.18入門精煉教程,由白丁入鴻儒,Go lang包管理機制(package)EP10

這就是一個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目錄下定址,這是我們需要注意的地方。