Go語言基於go module方式管理包(package)

尹正杰發表於2024-07-15

目錄
  • 一.Go Modules發展史
    • 1.前言
    • 2.早期第三方包儲存在GOPATH路徑
    • 3.vendor階段
    • 4.社群管理工具層出不窮
    • 5.go modules官宣官方管理工具
  • 二.go module介紹
    • 1.GO111MODULE環境變數
    • 2.GOPROXY
    • 3.GOSUMDB
    • 4.GONOPROXY/GONOSUMDB/GOPRIVATE
    • 5.go.mod檔案
    • 6.go.sum檔案
    • 7."go mod"依賴儲存位置
  • 三. go module常用命令
    • 1."go mod"常用的子命令概述
    • 2.使用go module引入包第三方包
      • 2.1 初始化專案
      • 2.2 下載第三方程式包
    • 3.程式碼測試
      • 3.1 專案組織結構
      • 3.2 blog.go原始碼
      • 3.3 main.go原始碼
      • 3.4 執行專案並訪問WebUI

一.Go Modules發展史

1.前言

一般程式語言都會提供依賴庫管理工具,例如python的pip、node.js的npm,java的maven,rust的cargo,Go語言也有提供自己的依賴庫管理工具。

Go語言在1.11提出了Go mod,每次版本或多或少都會對go.mod進行改進最佳化,go mod也越來越好,當前大多數公司都使用go mod來管理依賴庫。

考慮新手很容易在"go module"使用上犯迷糊,本篇文章主要介紹"Go Modules"發展史及快速入門的必要知識點。

2.早期第三方包儲存在GOPATH路徑

起初Go語言在1.5之前沒有依賴管理工具,若想引入依賴庫,需要執行go get命令將程式碼拉取放入GOPATH/src目錄下。

這樣的依賴管理方式存在一個致命的缺陷,那就是不支援版本管理,同一個依賴包只能存在一個版本的程式碼。

可是我們本地的多個專案完全可能分別依賴同一個第三方包的不同版本。

3.vendor階段

為了解決隔離專案的包依賴問題,Go1.5版本推出了vendor機制,環境變數中有一個GO15VENDOREXPERIMENT需要設定為1,該環境變數在Go1.6版本時變成預設開啟,目前已經退出了歷史舞臺;

vendor其實就是將原來放在"GOPATH/src"的依賴包放到工程的vendor目錄中進行管理,不同工程獨立地管理自己的依賴包,相互之間互不影響,原來是包共享的模式,透過vendor這種機制進行隔離,在專案編譯的時候會先去vendor目錄查詢依賴,如果沒有找到才會再去GOPATH目錄下查詢。

vendor階段的優劣勢:
	優點:
		保證了功能專案的完整性,減少了下載依賴包,直接使用vendor就可以編譯

	缺點:
		仍然沒有解決版本控制問題,go get仍然是拉取最新版本程式碼。

4.社群管理工具層出不窮

很多優秀的開發者在這期間也都實現了不錯的包依賴管理工具,例如:
  godep:
    https://github.com/tools/godep

  govendor:
    https://github.com/kardianos/govendor

  glide:
    https://github.com/Masterminds/glide

  dep:
    https://github.com/golang/dep

dep應該是其中最成功的,得到了Go語言官方的支援,該專案也被放到了https://github.com/golang/dep,但是為什麼dep沒有稱為官宣的依賴工具呢?

其實因為隨著"Russ Cox"與"Go團隊"中的其他成員不斷深入地討論,發現dep的一些細節似乎越來越不適合Go,因此官方採取了另起"proposal(建議)"的方式來推進,其方案的結果一開始先是釋出”vgo“,最終演變為我們現在所見到的”Go modules“;

5.go modules官宣官方管理工具

go modules是"Russ Cox"(Go語言核心開發團隊人員之一)推出來的,釋出於Go1.11。

go modules成長於Go1.12,豐富於Go1.13,正式於Go1.14推薦在生產上使用。

go modules幾乎後續的每個版本都或多或少的有一些最佳化,比如在Go1.16引入go mod retract、在Go1.18引入go work工作區的概念。

參考連線:
	https://www.cnblogs.com/yinzhengjie/p/18142081

二.go module介紹

1.GO111MODULE環境變數

這個環境變數是"Go Modules"的開關,主要有以下引數:
	auto:
		只在專案包含了go.mod檔案時啟動go modules,在Go1.13版本中是預設值。
	on:
		無腦啟動Go Modules,推薦設定,Go1.14版本以後的預設值。
	off:
		禁用Go Modules,一般沒有使用go modules的工程使用。
		
我現在使用的Go版本是1.22.4,預設GO111MODULE=on。

2.GOPROXY

這個環境變數主要是用於設定Go模組代理"Go module proxy",其作用是用於使Go在後續拉取模組版本時能夠脫離傳統的VCS方式,直接透過映象站點來快速拉取。

GOPROXY的預設值是"https://proxy.golang.org,direct",由於某些原因國內無法正常訪問該地址,所以我們通常需要配置一個可訪問的地址。目前社群使用比較多的有兩個"https://goproxy.cn"和"https://goproxy.io",當然如果你的公司有提供GOPROXY地址那麼就直接使用。

設定GOPAROXY的命令如下:
	# go env -w GOPROXY=https://goproxy.cn,direct

GOPROXY允許設定多個代理地址,多個地址之間需使用英文逗號","分隔。最後的"direct"是一個特殊指示符,用於指示Go回源到源地址去抓取(比如"GitHub"等)。

當配置有多個代理地址時,如果第一個代理地址返回"404"或"410"錯誤時,Go會自動嘗試下一個代理地址,當遇見"direct"時觸發回源,也就是回到源地址去抓取。

3.GOSUMDB

該環境變數的值是一個Go checksum database,用於保證Go在拉取模組版本時拉取到的模組版本資料未經篡改。

若發現不一致會中止,也可以將值設定為off即可以禁止Go在後續操作中校驗模組版本;

什麼是Go checksum database?

Go checksum database主要用於保護Go不會從任何拉到被篡改過的非法Go模組版本,詳細演算法機制可以看一下:
	https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md#proxying-a-checksum-database

GOSUMDB的預設值是sum.golang.org,預設值與自定義值的格式不一樣,預設值在國內是無法訪問,這個值我們一般不用動,因為我們一般已經設定好了GOPROXY,goproxy.cn支援代理sum.golang.org;

GOSUMDB的值自定義格式如下:
	- 格式1:
		<SUMDB_NAME>+<PUBLIC_KEY>。
	- 格式2:
		<SUMDB_NAME>+<PUBLIC_KEY> <SUMDB_URL>。

4.GONOPROXY/GONOSUMDB/GOPRIVATE

這三個環境變數放在一起說,一般在專案中不經常使用,這三個環境變數主要用於私有模組的拉取。

在GOPROXY、GOSUMDB中無法訪問到模組的場景中,例如拉取git上的私有倉庫。

GONOPROXY、GONOSUMDB的預設值是GOPRIVATE的值,所以我們一般直接使用GOPRIVATE即可,其值也是可以設定多個,以英文逗號進行分割。

"GOPRIVATE"用來告訴"go"命令哪些倉庫屬於私有倉庫,不必透過代理伺服器拉取和校驗。

"GOPRIVATE"的值也可以設定多個,多個地址之間使用英文逗號","分隔。我們通常會把自己公司內部的程式碼倉庫設定到"GOPRIVATE"中,例如:
	$ go env -w GOPRIVATE="git.yinzhengjie.com" 

這樣在拉取以"git.yinzhengjie.com"為路徑字首的依賴包時就能正常拉取了。

此外,如果公司內部自建了"GOPROXY"服務,那麼我們可以透過設定"GONOPROXY=none",允許從內部代理拉取私有倉庫的包。

也可以使用萬用字元的方式進行設定,對域名設定萬用字元號,這樣子域名就都不經過Go module proxy和Go checksum database;

5.go.mod檔案

go.mod是啟用Go modules的專案所必須且最重要的檔案,其描述了當前專案的元資訊,每個go.mod檔案開頭符合包含如下資訊:
	module:
		用於定義當前專案的模組路徑(突破$GOPATH路徑)。

	go:
		當前專案Go版本,目前只是標識作用、

	require:
		用設定一個特定的模組版本,可以指定需要依賴的版本號。
		Go modules中建議使用語義化版本控制,其建議的版本號格式如下:
			主版本號:
				釋出了不相容的版本迭代時遞增(breaking changes)。
			次版本號:
				釋出了功能性更新時遞增。
			修訂號:
				釋出了bug修復類更新時遞增。
 
	exclude:
		用於從使用中排除一個特定的模組版本。

	replace:
		用於將一個模組版本替換為另外一個模組版本。 
		比如將一個無法訪問的域名替換成國內的映象站點或者本地路徑均可。
		
	retract:
		用來宣告該第三方模組的某些發行版本不能被其他模組使用,在Go1.16引入。
		如果某個釋出的版本存在致命缺陷不再想讓使用者使用時,我們可以使用retract宣告廢棄的版本。
		
	indirect:
		行尾的indirect表示該依賴包為間接依賴,說明在當前程式中的所有"import"語句中沒有發現引入這個包。

6.go.sum檔案

使用go module下載了依賴後,專案目錄下還會生成一個go.sum檔案。

這個檔案中詳細記錄了當前專案中引入的依賴包的資訊及其hash 值。go.sum檔案內容通常是以類似下面的格式出現。
	- <module> <version>/go.mod <hash>
	- <module> <version> <hash>
	- <module> <version>/go.mod <hash>
	
不同於其他語言提供的基於中心的包管理機制,例如“npm”和“pypi”等,Go並沒有提供一箇中央倉庫來管理所有依賴包,而是採用分散式的方式來管理包。為了防止依賴包被非法篡改,Go module引入了go.sum機制來對依賴包進行校驗。

7."go mod"依賴儲存位置

"go mod download"會將依賴快取到本地,Go module快取的目錄是"$GOPATH/pkg/mod/cache“、"GOPATH/pkg/sum"。

這些快取依賴可以被多個專案使用,未來可能會遷移到$GOCACHE下面。
 
每個依賴包都會帶有版本號進行區分,這樣就允許在本地存在同一個包的多個不同版本。
 

溫馨提示:
	如果想清除所有本地已快取的依賴包資料,可以執行"go clean -modcache"命令。 

三. go module常用命令

1."go mod"常用的子命令概述

命令 介紹
go mod init 初始化專案依賴,生成go.mod檔案
go mod download 根據go.mod檔案下載依賴
go mod tidy 比對專案檔案中引入的依賴與go.mod進行比對
go mod graph 輸出依賴關係圖
go mod edit 編輯go.mod檔案
go mod vendor 將專案的所有依賴匯出至vendor目錄
go mod verify 檢驗一個依賴包是否被篡改過
go mod why 解釋為什麼需要某個依賴
Go module 是 Go1.11 版本釋出的依賴管理方案,從 Go1.14 版本開始推薦在生產環境使用,於Go1.16版本預設開啟。

如上表所示,Go module 提供了以上命令供我們使用,我們可以使用“go help mod”檢視可以使用的命令。

Go語言在"go module"的過渡階段提供了"GO111MODULE"這個環境變數來作為是否啟用"go module"功能的開關。

考慮到Go1.16之後 "go module"已經預設開啟,所以本書不再介紹該配置,對於剛接觸Go語言的讀者而言完全沒有必要了解這個歷史包袱。

2.使用go module引入包第三方包

2.1 初始化專案

	1.初始化專案
yinzhengjie@bogon 02-crm % pwd
/Users/yinzhengjie/golang/gosubjects/src/gocode/devops/05-package/02-crm
yinzhengjie@bogon 02-crm % 
yinzhengjie@bogon 02-crm % ls -l
total 0
yinzhengjie@bogon 02-crm % 
yinzhengjie@bogon 02-crm % go mod init web-server
go: creating new go.mod: module web-server
yinzhengjie@bogon 02-crm % 
yinzhengjie@bogon 02-crm % ls -l                 
total 8
-rw-r--r--@ 1 yinzhengjie  staff  29  7 14 23:00 go.mod
yinzhengjie@bogon 02-crm % 
yinzhengjie@bogon 02-crm % cat go.mod 
module web-server

go 1.22.4
yinzhengjie@bogon 02-crm % 
	
	
	2.go.mod內容說明:
module web-server:
	定義當前專案的匯入路徑。
	
go 1.22.4:
	標識當前專案使用的go版本。
		
		
溫馨提示:
	go.mod檔案會記錄專案使用的第三方依賴包資訊,包括包名和版本,由於我們的"web-server"專案目前還沒有使用到第三方依賴包,所以go.mod檔案暫時還沒有記錄任何依賴包資訊,只有當前專案的一些資訊。

2.2 下載第三方程式包

	1.安裝gin框架
yinzhengjie@bogon 02-crm % go get -u github.com/gin-gonic/gin


	2.檢視本地
yinzhengjie@bogon 02-crm % ls -l
total 24
-rw-r--r--@ 1 yinzhengjie  staff  1376  7 14 23:17 go.mod  # 依賴檔案
-rw-r--r--@ 1 yinzhengjie  staff  7028  7 14 23:17 go.sum  # 依賴包的校驗檔案
yinzhengjie@bogon 02-crm % 
yinzhengjie@bogon 02-crm % cat go.mod  # 關於go.mod的檔案內容解析請參考上文中的介紹喲~
module web-server

go 1.22.4

require (
        github.com/bytedance/sonic v1.11.9 // indirect
        github.com/bytedance/sonic/loader v0.1.1 // indirect
        github.com/cloudwego/base64x v0.1.4 // indirect
        github.com/cloudwego/iasm v0.2.0 // indirect
        github.com/gabriel-vasile/mimetype v1.4.4 // indirect
        github.com/gin-contrib/sse v0.1.0 // indirect
        github.com/gin-gonic/gin v1.10.0 // indirect
        github.com/go-playground/locales v0.14.1 // indirect
        github.com/go-playground/universal-translator v0.18.1 // indirect
        github.com/go-playground/validator/v10 v10.22.0 // indirect
        github.com/goccy/go-json v0.10.3 // indirect
        github.com/json-iterator/go v1.1.12 // indirect
        github.com/klauspost/cpuid/v2 v2.2.8 // indirect
        github.com/leodido/go-urn v1.4.0 // indirect
        github.com/mattn/go-isatty v0.0.20 // indirect
        github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
        github.com/modern-go/reflect2 v1.0.2 // indirect
        github.com/pelletier/go-toml/v2 v2.2.2 // indirect
        github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
        github.com/ugorji/go/codec v1.2.12 // indirect
        golang.org/x/arch v0.8.0 // indirect
        golang.org/x/crypto v0.25.0 // indirect
        golang.org/x/net v0.27.0 // indirect
        golang.org/x/sys v0.22.0 // indirect
        golang.org/x/text v0.16.0 // indirect
        google.golang.org/protobuf v1.34.2 // indirect
        gopkg.in/yaml.v3 v3.0.1 // indirect
)
yinzhengjie@bogon 02-crm %  



溫馨提示:
	1.在執行go get命令下載一個新的依賴包時一般會額外新增"-u"引數,強制更新現有依賴;
	2.行尾的indirect表示該依賴包為間接依賴,說明在當前程式中的所有"import"語句中沒有發現引入這個包;
	3.下載軟體包的名稱和版本使用"@"區分,若不指定版本則預設會下載最新的釋出版本;

3.程式碼測試

3.1 專案組織結構

如上圖所示,是我隨意找的目錄,做的目錄組織結構,其中包括引入第三方包和本專案中自定義包引入。

3.2 blog.go原始碼

package blog

import (
	// 匯入第三方倉庫程式碼
	"github.com/gin-gonic/gin"
)

func StartWebServer() {
	// 建立一個預設的路由引擎
	r := gin.Default()

	// GET:請求方式;/hello:請求的路徑
	// 當客戶端以GET方法請求/hello路徑時,會執行後面的匿名函式
	r.GET("/hello", func(c *gin.Context) {
		// c.JSON:返回JSON格式的資料
		c.JSON(200, gin.H{
			"name": "尹正傑",
			"blog": "https://www.cnblogs.com/yinzhengjie",
		})
	})

	// 啟動HTTP服務,預設在0.0.0.0:8080啟動服務
	r.Run()
}

3.3 main.go原始碼

package main

import (
	// 注意,此處的"web-server"是"go.mod檔案中記錄的"module web-server"對應的名稱喲~
	// 而"web-server"下對應的"blog"是我們自定義的包名喲~
	"web-server/blog"
)

func main() {
	blog.StartWebServer()
}

3.4 執行專案並訪問WebUI

如上圖所示,我們可以正常訪問咱們自定義的專案啦~

相關文章