【Go語言入門】一文搞懂Go語言的最新依賴管理:go mod的使用

一文搞懂Go語言的最新依賴管理:go mod的使用

2.1 背景

Go 依賴管理的演進經歷了以下 3 個階段:

在這裏插入圖片描述

目前被廣泛應用的是 Go Module,整個演進路線主要圍繞實現兩個目標來迭代發展:

  • 不同環境 (項目) 依賴的版本不同;
  • 控制依賴庫的版本。

2.2 Go 依賴管理的演進

2.2.1 GOPATH

GOPATH 是 Go 語言支持的一個環境變量,是 Go 項目的工作區。其目錄有以下 3 個結構 (需要手動創建文件夾):

image-20220517141135083

文件夾 作用
bin 項目編譯的二進制文件
pkg 項目編譯的中間產物
src 項目源碼
  • 項目代碼直接依賴 src 下的代碼;
  • go get 下載最新版本的包到 src 目錄下。

2.弊端

下面的場景就體現了 GOPATH 的弊端:項目A 和項B 依賴於某一 package 的不同版本 (分別爲 Pkg V1Pkg V2 ) 。而 src 下只能允許一個版本存在,那項目A 和項B 就無法保證都能編譯通過。

image-20220517141844767

在 GOPATH 管理模式下,如果多個項目依賴同一個庫,則依賴該庫是同一份代碼,無法做到不同項目依賴同一個庫的不同版本。這顯然無法滿足實際開發中的項目依賴需求,爲了解決這個問題,Go Vendor 出現了。

2.2.2 Go Vendor

  • 與 GOPATH 不同之處在於項目目錄下增加了 vendor 文件,所有依賴包以副本形式放在 $ProjectRoot/vendor 下。
  • 在 Vendor 機制下,如果當前項目存在 Vendor 目錄,會優先使用該目錄下的依賴;如果依賴不存在,則會從 GOPATH 中尋找。這樣,通過每個項目引入一份依賴的副本,解決了多個項目需要同一個 package 依賴的衝突問題。

image-20220517143602660

2.弊端

但 Vendor 無法很好解決依賴包版本變動問題和一個項目依賴同一個包的不同版本的問題。

image-20220517144202958

如圖項目A 依賴 Package B 和 Package C,而 Package B 和 Package C 又依賴了 Package D 的不同版本。通過 Vendor 的管理模式不能很好地控制對於 Package D 的依賴版本。一旦更新項目,有可能出現依賴衝突,導致編譯出錯。歸根到底: Vendor 不能很清晰地標識依賴的版本概念

2.2.3 Go Module

  • Go Module 是 Go 語言官方推出的依賴管理系統,解決了之前依賴管理系統存在的諸如無法依賴同一個庫的多個版本等問題。
  • Go Module 自 Go1.11 開始引入,Go 1.16 默認開啓。可以在項目目錄下看到 go.mod 文件:
    image-20220517144835944
名稱 作用
go.mod 文件,管理依賴包版本
go get / go mod 指令,管理依賴包

【終極目標】定義版本規則和管理項目依賴關係。和 Java 中的 Maven 作用是一樣的。

2.3 Go Module實踐

2.3.1 依賴管理三要素

要素 對於工具
配置文件,描述依賴 go.mod
中心倉庫管理依賴庫 Proxy
本地工具 go get / go mod

2.3.2 依賴配置-go.mod

打開項目目錄下的 go.mod 文件,其文件結構主要分爲三部分:

image-20220517150510319

【module 路徑 (上圖的“依賴管理基本單元”)】用來標識一個 module,從 module 路徑可以看出從哪裏找到該 module 。例如,如果以 github 爲前綴開頭,表示可以從 Github 倉庫找到該 module 。依賴包的源代碼由 Github 託管,如果項目的子包想被單獨引用,則需要通過單獨的 init go.mod 文件進行管理。

【原生庫】依賴的原生 Go SDK 版本。

【單元依賴】每個依賴單元用 module路徑 + 版本號 來唯一標識。

2.3.3 依賴配置-version

  • GOPATH 和 Go Vendor 都是源碼副本方式依賴,沒有版本規則概念。
  • 而 go.mod 爲了方便管理,定義了版本規則。分爲語義化版本和基於 commit 僞版本兩個版本。

1.語義化版本

${MAJOR}.${MINOR}.${PATCH}
 
  • 1

如:V1.18.1、V1.8.0

名稱 含義
MAJOR 不同的MAJOR版本表示是不兼容的API。因此即使是同一個庫,MAJOR版本不同也會被認爲是不同的模塊
MINOR 通常是新增函數或功能,向後兼容
PATCH 一般是修復bug

2.基於 commit 僞版本

每次提交 commit 後,Go 都會默認生成一個僞版本號:

v0.0.0-yyyymmddhhmmss-abcdefgh1234
 
  • 1

如:v1.0.0-20220517152630-c38fb59326b7

名稱 含義
v0.0.0 版本前綴和語義化版本是一樣的
yyyymmddhhmmss 時間戳,提交Commit的時間
abcdefgh1234 校驗碼,包含12位的哈希前綴

2.3.4 依賴配置-indirect

2.3.2 節的 go.mod 文件圖中,細心觀察可以發現有些單元依賴帶有 // indirect 的後綴,這是一個特殊標識符,表示 go.mod 對應的當前 module 沒有直接導入的包,也就是非直接依賴 (即間接依賴) 。

image-20220517153440426

例如,一個依賴關係鏈爲:A->B->C 。其中,A->B 是直接依賴;而 A->C 是間接依賴。

2.3.5 依賴配置-incompatible

2.3.2 節的 go.mod 文件圖中,細心觀察可以發現有些單元依賴帶有 +incompatible 的後綴,這也是一個特殊標識符。對於 MAJOR 主版本在 V2 及以上的模塊,go.mod 會在模塊路徑增加 /vN 後綴 (如下圖中 example/lib5/v3 v3.0.2 )。這能讓 Go Module 按照不同的模塊來處理同一個項目不同 MAJOR 主版本的依賴。

  • 由於 Go Module 是在 Go 1.11 才實驗性地引入,所以在這個更新提出之前,已經有一些倉庫打上了 V2 或者更高版本的 tag 了。
  • 爲了兼容這部分倉庫,對於沒有 go.mod 文件並且 MAJOR 主版本在 V2 及以上的依賴,會在版本號後加上 +incompatible 後綴。表示可能會存在不兼容的源代碼。

image-20220517154220581

2.3.6 依賴配置-依賴圖

如下圖所示,Main 項目依賴項目A 和項目B ,且項目A 和項目B 分別依賴項目C 的 v1.3 和 v1.4 版本。最終編譯時,Go 所使用的項目C 的版本爲:v1.4 。

【總結】Go 選擇最低的兼容版本。

image-20220517160311299

2.3.7 依賴分發-回源

  • 依賴分發,即依賴從何處下載、如何下載的問題。
  • Go 的依賴絕大部分託管在 GitHub 上。Go Module 系統中定義的依賴,最終都可以對應到 GitHub 中某一項目的特定提交 (commit) 或版本。
  • 對於 go.mod 中定義的依賴,則直接可以從對應倉庫中下載指定依賴,從而完成依賴分發。

image-20220517161158838

2.弊端

直接使用 GitHub 倉庫下載依賴存在一些問題:

  • 首先,無法保證構建穩定性。代碼作者可以直接在 GitHub 上增加/修改/刪除軟件版本。
  • 無法保證依賴可用性。代碼作者可以直接在 GitHub 上刪除代碼倉庫,導致依賴不可用。
  • 第三,如果所有人都直接從 GitHub 上獲取依賴,會導致 GitHub 平臺負載壓力。

3.解決方案-Proxy

  • Go Proxy 就是解決上述問題的方案。Go Proxy 是一個服務站點,它會緩存 GitHub 中的代碼內容,緩存的代碼版本不會改變,並且在 GitHub 作者刪除了代碼之後也依然可用,從而實現了 “immutability” (不變性) 和 “available” (可用的) 的依賴分發。
  • 使用 Go Proxy 後,構建時會直接從 Go Proxy 站點拉取依賴。如下圖所示。

image-20220517183641065

2.3.8 GOPROXY的使用

  • Go Module 通過 GOPROXY 環境變量控制如何使用 Go Proxy 。

  • GOPROXY 是一個 Go Proxy 站點 URL 列表。

    GOPROXY = "https://proxy1.cn, https://proxy2.cn, direct"
     
    • 1
  • 上述代碼中,direct 表示源站 (如 GitHub) ,proxy1 proxy2 是兩個URL 站點。依賴尋址路徑爲:優先從 proxy1 下載依賴,如果 proxy1 不存在,再從 proxy2 尋找,如果 proxy2 不存在,則會回源到源站直接下載依賴,並緩存到 Go Proxy 站點中 (這種設計思路和 Redis 緩存與 MySQL 數據庫一模一樣)。

image-20220517184332536

2.3.9 工具-go get

go get example.org/pkg +...
 
  • 1

後面跟不同的指令能實現不同的功能:

指令 功能
@update 默認
@none 刪除依賴
@v1.1.2 下載指定tag版本,語義版本
@23dfdd5 下載特定的commit版本
@master 下載分支的最新commit

2.3.10 工具-go mod

指令 功能
init 初始化,創建go.mod文件
download 下載模塊到本地緩存
tidy 增加需要的依賴,刪除不需要的依賴

在實際開發中,儘量提交之前執行下 go tidy ,減少構建時無效依賴包的拉取。

2.4 go mod使用

2.4.1 設置GO111MODULE

Win + R 輸入 cmd 打開命令行,輸入:

go env

即可看到 GO111MODULE (默認情況是空的):

image-20220518162705957

GO111MODULE 有三個值:off、on 和 auto (默認值)

  • GO111MODULE=off :go命令行將不會支持module功能,尋找依賴包的方式將會沿用舊版本那種通過vendor目錄或者GOPATH模式來查找。
  • GO111MODULE=on :go命令行會使用modules,而一點也不會去GOPATH目錄下查找。
  • GO111MODULE=auto :默認值,go命令行將會根據當前目錄來決定是否啓用module功能。這種情況下可以分爲兩種情形:
    • 當前目錄在GOPATH/src之外且該目錄包含go.mod文件
    • 當前文件在包含go.mod文件的目錄下面。

【注】

  • 在使用go module時,GOPATH是無意義的。不過它仍然會把下載的依賴存儲在 $GOPATH/pkg/mod 中,也會把 go install 的結果放在 $GOPATH/bin 中。
  • 當module功能啓用時,依賴包的存放位置變更爲 $GOPATH/pkg 。允許同一個package多個版本並存,且多個項目可以共享緩存的module。

設置的命令如下:

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

可在命令行中輸入:go env 查看 GO111MODULE=on

2.4.2 清空所有GOPATH

開啓 go mod 之後,並不能與 GOPATH 共存。必須把項目從 GOPATH 中移除,否則會報 $GOPATH/go.mod exists but should not 的錯誤。

在 Goland 中,移除項目所有 GOPATH 的操作如下:

image-20220518160417108

清空 GOPATH 之後,在單元測試模式下,同一個包下不同文件函數調用報錯爲 undefined 的問題也會解決。

2.4.3 在新項目中創建go mod

注意:如果你的項目根目錄下已經有 go.mod 文件,可以不需要創建 go.mod 文件。
爲了演示如何管理依賴,我創建了 hello.go 文件和 hello_test.go 單元測試代碼:

單元測試的目錄結構如下圖所示:

image-20220518132408063

hello.go 代碼:

package hello

import "rsc.io/quote"//引入第三方依賴模塊

func Hello() string {
   return quote.Hello()
}

第 3 行代碼:需要導入第三方依賴模塊 rsc.io/quote

hello_test.go 代碼:

package hello

import "testing"

func TestHello(t *testing.T) {
   want := "Hello, world."
   if got := Hello(); got != want {
      t.Errorf("Hello() = %q, want %q", got, want)
   }
}

1.打開Windows終端命令行,cd 到新項目的文件夾目錄。輸入命令:

go mod init XXX(你的文件夾名稱)

image-20220518171656640

成功創建了 go.mod 文件,如下圖所示:

image-20220518165322088

2.【重點!】從 Go 1.16 開始,創建完 go.mod 文件還必須執行指令:

$ go mod tidy

來增加項目需要的最小依賴。否則,運行 go test 指令時會報 no Go files in G:\hellono required module provides package rsc.io/quote; to add it: go get rsc.io/quote 的錯誤。

運行結果如下圖所示,go mod 會自動拉取項目所需的最小依賴。

image-20220518204236617

此時我們可以打開看看 go.mod 文件中的內容:

$ cat go.mod

輸出:

module hello

go 1.18

require rsc.io/quote v1.5.2

require (
        golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
        rsc.io/sampler v1.3.0 // indirect
)

在 Goland 中可以雙擊直接打開:

image-20220518204405803

這樣,就成功編寫並測試了第一個模塊了。

2.4.4 go.mod文件結構

  • module :指定模塊的名稱 (路徑)
  • go :依賴的原生 Go SDK 版本
  • require :項目所依賴的模塊
  • replace :可以替換依賴的模塊
  • exclude :可以忽略依賴的模塊

2.4.5 添加依賴

在完成上述所有操作後,發現 hello.go 文件還是編譯不通過,如下圖所示:

image-20220518204513345

這時候我們再次執行 go test 指令,如下圖所示:

image-20220518204907404

發現 go mod 會自動查找依賴並自動下載。單元測試通過。

【注意】此時我們已經開啓go mod模式了,但Goland可能仍出現 hello.go 文件的 import 報紅的情況。

【解決方法】如下圖設置,Environment 處填寫的 GOPROXY 網址要與cmd命令行輸入 go env 中的 GOPROXY 保持一致。設置好後重啓Goland即可。

image-20220519092159634
image-20220519092439474

go module 安裝 package 的原則是先拉最新的 release tag,若無tag則拉最新的commit,詳見 Modules官方介紹。 go 會自動生成一個 go.sum 文件來記錄 dependency tree:

image-20220518205257609

  • 在代碼沒有改變的情況下,再次執行代碼會發現跳過了檢查並安裝依賴的步驟。
  • 可以使用命令 go list -m -u all 來檢查可以升級的 package ,使用 go get -u need-upgrade-package 升級後會將新的依賴版本更新到 go.mod 中。也可以使用 go get -u 升級所有依賴。

2.4.6 go get更新依賴

  • go get -u :更新到最新的次要版本或者修訂版本(x.y.z)
  • go get -u=patch :更新到最新的修訂版本
  • go get [email protected] :更新到指定的版本號version
  • 運行 go get 如果有版本的更改,那麼 go.mod 文件也會作出相應的更改。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章