亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

淺析Go項目中的依賴包管理與Go?Module常規(guī)操作

 更新時間:2023年10月25日 15:15:42   作者:賈維斯Echo  
這篇文章主要為大家詳細介紹了Go項目中的依賴包管理與Go?Module常規(guī)操作,文中的示例代碼講解詳細,對我們深入了解Go語言有一定的幫助,需要的可以跟隨小編一起學習一下

一.Go 構(gòu)建模式的演變

Go 程序由 Go 包組合而成的,Go 程序的構(gòu)建過程就是確定包版本、編譯包以及將編譯后得到的目標文件鏈接在一起的過程。

Go 構(gòu)建模式歷經(jīng)了三個迭代和演化過程,分別是最初期的 GOPATH、1.5 版本的 Vendor 機制,以及現(xiàn)在的 Go Module。

1.1 GOPATH (初版)

Go 語言在首次開源時,就內(nèi)置了一種名為 GOPATH 的構(gòu)建模式。

特點:在這種構(gòu)建模式下,Go 編譯器可以在本地 GOPATH 環(huán)境變量配置的路徑下,搜尋 Go 程序依賴的第三方包。如果存在,就使用這個本地包進行編譯;如果不存在,就會報編譯錯誤

首先使用go 多版本管理工具gvm 將 Go 版本到1.10.8:

# 如果沒有安裝gvm,使用如下命令安裝
bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
gvm install go1.10.8 # 使用 GVM 安裝 Go 1.10.8 
gvm use go1.10.8 # 切換到 Go 1.10.8 版本
go version # 驗證是否成功設置了 Go 1.10.8

這里給出了一段在 GOPATH 構(gòu)建模式下編寫的代碼:

package main

import "github.com/sirupsen/logrus"
func main() {
    logrus.Println("hello, gopath mode")
}

然后使用Go 1.10.8編譯執(zhí)行如下:

# go build main.go
# 直接報錯如下:
main.go:3:8: cannot find package "github.com/sirupsen/logrus" in any of:
        /usr/local/go/src/github.com/sirupsen/logrus (from $GOROOT)
        /root/go/src/github.com/sirupsen/logrus (from $GOPATH)

那么 Go 編譯器在 GOPATH 構(gòu)建模式下,究竟怎么在 GOPATH 配置的路徑下搜尋第三方依賴包呢?

為了說清楚搜尋規(guī)則,先假定 Go 程序?qū)肓?nbsp;github.com/user/repo 這個包,我們也同時假定當前 GOPATH 環(huán)境變量配置的值為:

export GOPATH=/usr/local/goprojects:/root/go/

那么在 GOPATH 構(gòu)建模式下,Go 編譯器在編譯 Go 程序時,就會在下面兩個路徑下搜索第三方依賴包是否存在:

/usr/local/goprojects/src/github.com/user/repo
/root/go/src/github.com/user/repo

注意:如果你沒有顯式設置 GOPATH 環(huán)境變量,Go 會將 GOPATH 設置為默認值,不同操作系統(tǒng)下默認值的路徑不同,在 macOS 或 Linux 上,它的默認值是 $HOME/go

當本地找不到第三方依賴包的情況,我們該如何解決這個問題呢?

這個時候就需要讓 go get 登場了!

1.1.1 go get

在本地沒有找到程序的第三方依賴包,可以通過 go get 命令將本地缺失的第三方依賴包下載到本地,比如:

go get github.com/sirupsen/logrus

這里的go get命令會下載第三方Go包及其依賴到本地的GOPATH目錄下。并且go get 下載的包只是那個時刻各個依賴包的最新主線版本,這樣會給后續(xù) Go 程序的構(gòu)建帶來一些問題。比如,依賴包持續(xù)演進,可能會導致不同開發(fā)者在不同時間獲取和編譯同一個 Go 包時,得到不同的結(jié)果,也就是不能保證可重現(xiàn)的構(gòu)建(Reproduceable Build)。又比如,如果依賴包引入了不兼容代碼,程序?qū)o法通過編譯。

最后還有一點,如果依賴包因引入新代碼而無法正常通過編譯,并且該依賴包的作者又沒用及時修復這個問題,這種錯誤也會傳導到你的程序,導致你的程序無法通過編譯。

在 GOPATH 構(gòu)建模式下,Go 編譯器實質(zhì)上并沒有關(guān)注 Go 項目所依賴的第三方包的版本。但 Go 開發(fā)者希望自己的 Go 項目所依賴的第三方包版本能受到自己的控制,而不是隨意變化。所以 Go 核心開發(fā)團隊引入了 Vendor 機制試圖解決上面的問題。

1.2 vendor 機制(中版)

Go 在 1.5 版本中引入 vendor 機制。所謂 vendor 機制,就是每個項目的根目錄下可以有一個 vendor 目錄,里面存放了該項目的依賴的 package。go build 的時候會先去 vendor 目錄查找依賴,如果沒有找到會再去 GOPATH 目錄下查找。

這樣的話,Go 編譯器會優(yōu)先感知和使用 vendor 目錄下緩存的第三方包版本,而不是 GOPATH 環(huán)境變量所配置的路徑下的第三方包版本。這樣,無論第三方依賴包自己如何變化,無論 GOPATH 環(huán)境變量所配置的路徑下的第三方包是否存在、版本是什么,都不會影響到 Go 程序的構(gòu)建。

如果使用 vendor 機制管理第三方依賴包,最佳實踐就是將 vendor 一并提交到代碼倉庫中。那么其他開發(fā)者下載你的項目后,就可以實現(xiàn)可重現(xiàn)的構(gòu)建。

下面這個目錄結(jié)構(gòu)就是為上面的代碼示例添加 vendor 目錄后的結(jié)果:

.
├── main.go
└── vendor/
    ├── github.com/
    │   └── sirupsen/
    │       └── logrus/
    └── golang.org/
        └── x/
            └── sys/
                └── unix/

在添加完 vendor 后,我們重新編譯 main.go,這個時候 Go 編譯器就會在 vendor 目錄下搜索程序依賴的 logrus 包以及后者依賴的 golang.org/x/sys/unix 包了.

注意:要想開啟 vendor 機制,你的 Go 項目必須位于 GOPATH 環(huán)境變量配置的某個路徑的 src 目錄下面。如果不滿足這一路徑要求,那么 Go 編譯器是不會理會 Go 項目目錄下的 vendor 目錄的

不過 vendor 機制雖然一定程度解決了 Go 程序可重現(xiàn)構(gòu)建的問題,但對開發(fā)者來說,它的體驗卻不那么好。一方面,Go 項目必須放在 GOPATH 環(huán)境變量配置的路徑下,龐大的 vendor 目錄需要提交到代碼倉庫,不僅占用代碼倉庫空間,減慢倉庫下載和更新的速度,而且還會干擾代碼評審,對實施代碼統(tǒng)計等開發(fā)者效能工具也有比較大影響。另外,你還需要手工管理 vendor 下面的 Go 依賴包,包括項目依賴包的分析、版本的記錄、依賴包獲取和存放等等。

為解決這個問題,Go 核心團隊與社區(qū)將 Go 構(gòu)建的重點轉(zhuǎn)移到如何解決包依賴管理上。Go 社區(qū)先后開發(fā)了諸如 gbglide、dep 等工具,來幫助 Go 開發(fā)者對 vendor 下的第三方包進行自動依賴分析和管理,但這些工具也都有自身的問題。

Go 核心團隊基于社區(qū)實踐的經(jīng)驗和教訓,推出了 Go 官方的最新解決方案:Go Module。

1.3 Go Module(最新版)

Go 1.11 版本推出 modules 機制,簡稱 mod,更加易于管理項目中所需要的模塊。

一個 Go Module 是一個 Go 包的集合。module 是有版本的,所以 module 下的包也就有了版本屬性。這個 module 與這些包會組成一個獨立的版本單元,它們一起打版本、發(fā)布和分發(fā),。

在 Go Module 模式下,通常一個代碼倉庫對應一個 Go Module。一個 Go Module 的頂層目錄下會放置一個 go.mod 文件,每個 go.mod 文件會定義唯一一個 module,也就是說 Go Module 與 go.mod 是一一對應的。

并且其根目錄中包含 go.mod 文件,go.mod 文件定義了模塊的模塊路徑,它也是用于根目錄的導入路徑,以及它的依賴性要求。每個依賴性要求都被寫為模塊路徑和特定語義版本。

go.mod 文件所在的頂層目錄也被稱為 module 的根目錄,module 根目錄以及它子目錄下的所有 Go 包均歸屬于這個 Go Module,這個 module 也被稱為 main module

從 Go 1.11 開始,Go 允許在 $GOPATH/src 外的任何目錄下使用 go.mod 創(chuàng)建項目。在 $GOPATH/src 中,為了兼容性,Go 命令仍然在舊的 GOPATH 模式下運行。從 Go 1.13 開始,go.mod模式將成為默認模式。

二.創(chuàng)建Go Module

2.1 創(chuàng)建步驟

將基于當前項目創(chuàng)建一個 Go Module,通常有如下幾個步驟:

  • 通過 go mod init [項目地址\庫地址] 創(chuàng)建 go.mod 文件,將當前項目變?yōu)橐粋€ Go Module;
  • 通過 go mod tidy 命令自動更新當前 module 的依賴信息;
  • 執(zhí)行 go build,執(zhí)行新 module 的構(gòu)建。

2.2 簡單舉列

新建一個main.go文件,引入外部包 logrus

package main

import "github.com/sirupsen/logrus"
func main() {
  logrus.Println("hello, go module mode")
}

我們通過 go mod init 命令為這個項目創(chuàng)建一個 Go Module(這里我們使用的是 Go 版本最新版,Go 最新版默認采用 Go Module 構(gòu)建模式)

$go mod init github.com/bigwhite/module-mode
go: creating new go.mod: module github.com/bigwhite/module-mode
go: to add module requirements and sums:
  go mod tidy

現(xiàn)在,go mod init 在當前項目目錄下創(chuàng)建了一個 go.mod 文件,這個 go.mod 文件將當前項目變?yōu)榱艘粋€ Go Module,項目根目錄變成了 module 根目錄。go.mod 的內(nèi)容是這樣的.

module github.com/bigwhite/module-mode
go 1.21.1

這個 go.mod 文件現(xiàn)在處于初始狀態(tài),它的第一行內(nèi)容用于聲明 module 路徑(module path),一般是指定自己項目的git地址,最后一行是 Go 版本指示符,表示這個 module 是在某個特定的 Go 版本的 module 語義的基礎上編寫的。

go mod init 命令日志輸出提示我們可以使用 go mod tidy 命令,添加 module 依賴以及校驗和。go mod tidy 命令會掃描 Go 源碼,并自動找出項目依賴的外部 Go Module 以及版本,下載這些依賴并更新本地的 go.mod 文件。我們按照這個提示執(zhí)行一下 go mod tidy 命令

$go mod tidy                                   
go: finding module for package github.com/sirupsen/logrus
go: downloading github.com/sirupsen/logrus v1.9.3
go: found github.com/sirupsen/logrus in github.com/sirupsen/logrus v1.9.3
go: downloading golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8
go: downloading github.com/stretchr/testify v1.7.0
go: downloading gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c

我們看到,對于一個處于初始狀態(tài)的 module 而言,go mod tidy 分析了當前 main module 的所有源文件,找出了當前 main module 的所有第三方依賴,確定第三方依賴的版本,還下載了當前 main module 的直接依賴包(比如 logrus),以及相關(guān)間接依賴包(直接依賴包的依賴,比如上面的 golang.org/x/sys 等)。

由 go mod tidy 下載的依賴 module 會被放置在本地的 module 緩存路徑下,默認值為 $GOPATH[0]/pkg/mod,Go 1.15 及以后版本可以通過 GOMODCACHE 環(huán)境變量,自定義本地 module 的緩存路徑。

執(zhí)行 go mod tidy 后,我們示例 go.mod 的內(nèi)容更新如下:

module github.com/bigwhite/module-mode
go 1.21.1
require github.com/sirupsen/logrus v1.9.3
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect

可以看到,當前 module 的直接依賴 logrus,還有它的版本信息都被寫到了 go.mod 文件的 require 段中。而且,執(zhí)行完go mod tidy后,當前項目除了 go.mod 文件外,還多了一個新文件 go.sum,內(nèi)容是這樣的:

github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

這同樣是由 go mod 相關(guān)命令維護的一個文件,它存放了特定版本 module 內(nèi)容的哈希值。這是 Go Module 的一個安全措施。當將來這里的某個 module 的特定版本被再次下載的時候,go 命令會使用 go.sum 文件中對應的哈希值,和新下載的內(nèi)容的哈希值進行比對,只有哈希值比對一致才是合法的,這樣可以確保你的項目所依賴的 module 內(nèi)容,不會被惡意或意外篡改。因此,我推薦你把 go.mod 和 go.sum 兩個文件與源碼,一并提交到代碼版本控制服務器上。

接下來,我們只需在當前 module 的根路徑下,執(zhí)行 go build 就可以完成 module 的構(gòu)建了!

$go build
$$ls
go.mod    go.sum    main.go    module-mode
$./module-mode 
INFO[0000] hello, go module mode

整個過程的執(zhí)行步驟是這樣:go build 命令會讀取 go.mod 中的依賴及版本信息,并在本地 module 緩存路徑下找到對應版本的依賴 module,執(zhí)行編譯和鏈接。如果順利的話,我們會在當前目錄下看到一個新生成的可執(zhí)行文件 module-mode,執(zhí)行這個文件我們就能得到正確結(jié)果了。

三.深入理解 Go Module 構(gòu)建模式

Go 語言設計者在設計 Go Module 構(gòu)建模式,來解決“包依賴管理”的問題時,進行了幾項創(chuàng)新,這其中就包括語義導入版本 (Semantic Import Versioning),以及和其他主流語言不同的最小版本選擇 (Minimal Version Selection) 等機制。

3.1 Go Module 的語義導入版本機制

在上面的例子中,我們看到 go.mod 的 require 段中依賴的版本號,都符合 vX.Y.Z 的格式。在 Go Module 構(gòu)建模式下,一個符合 Go Module 要求的版本號,由前綴 v 和一個滿足語義版本規(guī)范的版本號組成。例如,上面的 logrus module 的版本號是 v1.9.3,這就表示它的主版本號為 1,次版本號為 9,補丁版本號為 3.

語義版本號分成 3 部分:

  • 主版本號 (major)
  • 次版本號 (minor)
  • 補丁版本號 (patch)

Go 命令和 go.mod 文件都使用上面這種符合語義版本規(guī)范的版本號,作為描述 Go Module 版本的標準形式。借助于語義版本規(guī)范,Go 命令可以確定同一 module 的兩個版本發(fā)布的先后次序,而且可以確定它們是否兼容。

按照語義版本規(guī)范,主版本號不同的兩個版本是相互不兼容的。而且,在主版本號相同的情況下,次版本號大都是向后兼容次版本號小的版本。補丁版本號也不影響兼容性。

而且,Go Module 規(guī)定:如果同一個包的新舊版本是兼容的,那么它們的包導入路徑應該是相同的。

怎么理解呢?我們來舉個簡單示例。我們就以 logrus 為例,它有很多發(fā)布版本,我們從中選出兩個版本 v1.7.0 和 v1.8.1.。按照上面的語義版本規(guī)則,這兩個版本的主版本號相同,新版本 v1.8.1 是兼容老版本 v1.7.0 的。那么,我們就可以知道,如果一個項目依賴 logrus,無論它使用的是 v1.7.0 版本還是 v1.8.1 版本,它都可以使用下面的包導入語句導入 logrus 包:

import "github.com/sirupsen/logrus"

Go Module 創(chuàng)新性地給出了一個方法:將包主版本號引入到包導入路徑中,我們可以像下面這樣導入 logrus v2.0.0 版本依賴包:

import "github.com/sirupsen/logrus/v2"

這就是 Go 的“語義導入版本”機制,也就是說通過在包導入路徑中引入主版本號的方式,來區(qū)別同一個包的不兼容版本,這樣一來我們甚至可以同時依賴一個包的兩個不兼容版本:

import (
    "github.com/sirupsen/logrus"
    logv2 "github.com/sirupsen/logrus/v2"
)

不過到這里,你可能會問,v0.y.z 版本應該使用哪種導入路徑呢?

按照語義版本規(guī)范的說法,v0.y.z 這樣的版本號是用于項目初始開發(fā)階段的版本號。在這個階段任何事情都有可能發(fā)生,其 API 也不應該被認為是穩(wěn)定的。Go Module 將這樣的版本 (v0) 與主版本號 v1 做同等對待,也就是采用不帶主版本號的包導入路徑,這樣一定程度降低了 Go 開發(fā)人員使用這樣版本號包時的心智負擔。

Go 語義導入版本機制是 Go Module 機制的基礎規(guī)則,同樣它也是 Go Module 其他規(guī)則的基礎。

3.2 Go Module 的最小版本選擇原則

在前面的例子中,Go 命令都是在項目初始狀態(tài)分析項目的依賴,并且項目中兩個依賴包之間沒有共同的依賴,這樣的包依賴關(guān)系解決起來還是比較容易的。但依賴關(guān)系一旦復雜起來,比如像下圖中展示的這樣,Go 又是如何確定使用依賴包 C 的哪個版本的呢?

在這張圖中,myproject 有兩個直接依賴 A 和 B,A 和 B 有一個共同的依賴包 C,但 A 依賴 C 的 v1.1.0 版本,而 B 依賴的是 C 的 v1.3.0 版本,并且此時 C 包的最新發(fā)布版為 C v1.7.0。這個時候,Go 命令是如何為 myproject 選出間接依賴包 C 的版本呢?

其實,當前存在的主流編程語言,以及 Go Module 出現(xiàn)之前的很多 Go 包依賴管理工具都會選擇依賴項的“最新最大 (Latest Greatest) 版本”,對應到圖中的例子,這個版本就是 v1.7.0。

當然了,理想狀態(tài)下,如果語義版本控制被正確應用,并且這種“社會契約”也得到了很好的遵守,那么這種選擇算法是有道理的,而且也可以正常工作。在這樣的情況下,依賴項的“最新最大版本”應該是最穩(wěn)定和安全的版本,并且應該有向后兼容性。至少在相同的主版本 (Major Verion) 依賴樹中是這樣的

但我們這個問題的答案并不是這樣的。Go 設計者另辟蹊徑,在諸多兼容性版本間,他們不光要考慮最新最大的穩(wěn)定與安全,還要尊重各個 module 的述求:A 明明說只要求 C v1.1.0,B 明明說只要求 C v1.3.0。所以 Go 會在該項目依賴項的所有版本中,選出符合項目整體要求的“最小版本”.

這個例子中,C v1.3.0 是符合項目整體要求的版本集合中的版本最小的那個,于是 Go 命令選擇了 C v1.3.0,而不是最新最大的 C v1.7.0。并且,Go 團隊認為“最小版本選擇”為 Go 程序?qū)崿F(xiàn)持久的和可重現(xiàn)的構(gòu)建提供了最佳的方案。

即:對于導入路徑不同的包,則兩個包是同時被依賴的,導入路徑相同,則只能選擇依賴一個,并且會選擇所有直接間接依賴這個包的版本的最高版本,而不是該包本身的最高版本,這是所有依賴這個包的其他包都能接受的最小版本,這樣可以保證服務整體的穩(wěn)定性。

3.3 Go 各版本構(gòu)建模式機制和切換

在 Go 1.11 版本中,Go 開發(fā)團隊引入 Go Modules 構(gòu)建模式。這個時候,GOPATH 構(gòu)建模式與 Go Modules 構(gòu)建模式各自獨立工作,我們可以通過設置環(huán)境變量 GO111MODULE 的值在兩種構(gòu)建模式間切換。

然后,隨著 Go 語言的逐步演進,從 Go 1.11 到 Go 1.16 版本,不同的 Go 版本在 GO111MODULE 為不同值的情況下,開啟的構(gòu)建模式幾經(jīng)變化,直到 Go 1.16 版本,Go Module 構(gòu)建模式成為了默認模式。

所以,要分析 Go 各版本的具體構(gòu)建模式的機制和切換,我們只需要找到這幾個代表性的版本就好了。

我這里將 Go 1.13 版本之前、Go 1.13 版本以及 Go 1.16 版本,在 GO111MODULE 為不同值的情況下的行為做了一下對比,這樣我們可以更好地理解不同版本下、不同構(gòu)建模式下的行為特性,下面我們就來用表格形式做一下比對:

四.設置 GO111MODULE

要啟用go module支持首先要設置環(huán)境變量GO111MODULE,通過它可以開啟或關(guān)閉模塊支持,它有三個可選值:off、on、auto,默認值是auto。

  • GO111MODULE=off禁用模塊支持,編譯時會從GOPATH和vendor文件夾中查找包。
  • GO111MODULE=on啟用模塊支持,編譯時會忽略GOPATH和vendor文件夾,只根據(jù) go.mod下載依賴。
  • GO111MODULE=auto,當項目在$GOPATH/src外且項目根目錄有g(shù)o.mod文件時,開啟模塊支持。

設置Go Model

# 臨時開啟 Go modules 功能
export GO111MODULE=on
# 永久開啟 Go modules 功能
go env -w GO111MODULE=on

五.Go module 常用操作

5.1初始化項目

基于當前項目創(chuàng)建一個 Go Module,通常有如下幾個步驟:

  • 通過 go mod init 項目名 創(chuàng)建 go.mod 文件,將當前項目變?yōu)橐粋€ Go Module;
  • 通過 go mod tidy 命令自動更新當前 module 的依賴信息;
  • 執(zhí)行 go build,執(zhí)行新 module 的構(gòu)建。

然后會生成兩個文件go.modgo.sum.

5.1.1 go.mod

go.mod文件記錄了項目所有的依賴信息,其結(jié)構(gòu)大致如下:

module dome
go 1.18
require (
	github.com/google/uuid v1.3.0
	github.com/sirupsen/logrus v1.9.0
)

require (
	github.com/kr/fs v0.1.0 // indirect 
	github.com/kr/pretty v0.3.1 // indirect
	github.com/kr/text v0.2.0 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	github.com/rogpeppe/go-internal v1.9.0 // indirect
	github.com/tools/godep v0.0.0-20180126220526-ce0bfadeb516 // indirect
	golang.org/x/sys v0.3.0 // indirect
	golang.org/x/tools v0.4.0 // indirect
)

其中,

  • module用來模塊名稱
  • require用來定義依賴包及版本
  • exclude 禁止依賴包列表,不下載和引用哪些包(僅在當前模塊為主模塊時生效)
  • replace 替換依賴包列表和引用路徑(僅在當前模塊為主模塊時生效)
  • indirect 表示這個庫是間接引用進來的。

5.1.2 replace

在國內(nèi)訪問golang.org/x的各個包都需要FQ,你可以在go.mod中使用replace替換成github上對應的庫。

replace (
	golang.org/x/net => github.com/golang/net latest
	golang.org/x/tools => github.com/golang/tools latest
	golang.org/x/crypto => github.com/golang/crypto latest
	golang.org/x/sys => github.com/golang/sys latest
	golang.org/x/text => github.com/golang/text latest
	golang.org/x/sync => github.com/golang/sync latest
)

5.1.3 go 查看當前項目所有包依賴

使用 go list -m all 可以查看到所有依賴列表,也可以使用 go list -json -m all 輸出 json格式的打印結(jié)果。

5.2 升級 / 降級版本

首先,查看依賴歷史版本

$go list -m -versions github.com/sirupsen/logrus
github.com/sirupsen/logrus v0.1.0 v0.1.1 v0.2.0 v0.3.0 v0.4.0 v0.4.1 v0.5.0 v0.5.1 v0.6.0 v0.6.1 v0.6.2 v0.6.3 v0.6.4 v0.6.5 v0.6.6 v0.7.0 v0.7.1 v0.7.2 v0.7.3 v0.8.0 v0.8.1 v0.8.2 v0.8.3 v0.8.4 v0.8.5 v0.8.6 v0.8.7 v0.9.0 v0.10.0 v0.11.0 v0.11.1 v0.11.2 v0.11.3 v0.11.4 v0.11.5 v1.0.0 v1.0.1 v1.0.3 v1.0.4 v1.0.5 v1.0.6 v1.1.0 v1.1.1 v1.2.0 v1.3.0 v1.4.0 v1.4.1 v1.4.2 v1.5.0 v1.6.0 v1.7.0 v1.7.1 v1.8.0 v1.8.1

我們可以在項目的 module 根目錄下,執(zhí)行帶有版本號的 go get 命令:

$go get github.com/sirupsen/logrus@v1.7.0
go: downloading github.com/sirupsen/logrus v1.7.0
go get: downgraded github.com/sirupsen/logrus v1.8.1 => v1.7.0

當然我們也可以使用萬能命令 go mod tidy來幫助我們降級,但前提是首先要用 go mod edit 命令,明確告知我們要依賴 v1.7.0 版本,而不是 v1.8.1,這個執(zhí)行步驟是這樣的:

$go mod edit -require=github.com/sirupsen/logrus@v1.7.0
$go mod tidy       
go: downloading github.com/sirupsen/logrus v1.7.0

升級版本和降級版本依賴一樣,參照上面的操作即可,

5.3 刪除未使用的依賴

可以用 go mod tidy 命令來清除這些沒用到的依賴項:

go mod tidy

go mod tidy會自動分析源碼依賴,而且將不再使用的依賴從 go.mod 和 go.sum 中移除。

5.4 引入主版本號大于 1 的三方庫

語義導入版本機制有一個原則:如果新舊版本的包使用相同的導入路徑,那么新包與舊包是兼容的。也就是說,如果新舊兩個包不兼容,那么我們就應該采用不同的導入路徑。

按照語義版本規(guī)范,如果我們要為項目引入主版本號大于 1 的依賴,比如 v2.0.0,那么由于這個版本與 v1、v0 開頭的包版本都不兼容,我們在導入 v2.0.0 包時,不能再直接使用 github.com/user/repo,而要使用像下面代碼中那樣不同的包導入路徑:

import github.com/user/repo/v2/xxx

也就是說,如果我們要為 Go 項目添加主版本號大于 1 的依賴,我們就需要使用“語義導入版本”機制,在聲明它的導入路徑的基礎上,加上版本號信息。我們以“向 module-mode 項目添加 github.com/go-redis/redis 依賴包的 v7 版本”為例,看看添加步驟。

首先,我們在源碼中,以空導入的方式導入 v7 版本的 github.com/go-redis/redis 包:

package main

import (
  _ "github.com/go-redis/redis/v7" // “_”為空導入
  "github.com/google/uuid"
  "github.com/sirupsen/logrus"
)

func main() {
  logrus.Println("hello, go module mode")
  logrus.Println(uuid.NewString())
}

接下來我們通過 go get 獲取redis的v7版本:

$go get github.com/go-redis/redis/v7
go: downloading github.com/go-redis/redis/v7 v7.4.1
go: downloading github.com/go-redis/redis v6.15.9+incompatible
go get: added github.com/go-redis/redis/v7 v7.4.1

5.5 升級依賴版本到一個不兼容版本

我們前面說了,按照語義導入版本的原則,不同主版本的包的導入路徑是不同的。所以,同樣地,我們這里也需要先將代碼中 redis 包導入路徑中的版本號改為 v8:

import (
  _ "github.com/go-redis/redis/v8"
  "github.com/google/uuid"
  "github.com/sirupsen/logrus"
)

接下來,我們再通過 go get 來獲取 v8 版本的依賴包:

$go get github.com/go-redis/redis/v8
go: downloading github.com/go-redis/redis/v8 v8.11.1
go: downloading github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
go: downloading github.com/cespare/xxhash/v2 v2.1.1
go get: added github.com/go-redis/redis/v8 v8.11.1

5.6 特殊情況:使用 vendor

vendor 機制雖然誕生于 GOPATH 構(gòu)建模式主導的年代,但在 Go Module 構(gòu)建模式下,它依舊被保留了下來,并且成為了 Go Module 構(gòu)建機制的一個很好的補充。特別是在一些不方便訪問外部網(wǎng)絡,并且對 Go 應用構(gòu)建性能敏感的環(huán)境。

和GOPATH 構(gòu)建模式不同,Go Module 構(gòu)建模式下,我們再也無需手動維護 vendor 目錄下的依賴包了,Go 提供了可以快速建立和更新 vendor 的命令,我們還是以前面的module-mode 項目為例,通過下面命令為該項目建立 vendor:

$go mod vendor
$tree -LF 2 vendor
vendor
├── github.com/
│   ├── google/
│   ├── magefile/
│   └── sirupsen/
├── golang.org/
│   └── x/
└── modules.txt

我們看到,go mod vendor 命令在 vendor 目錄下,創(chuàng)建了一份這個項目的依賴包的副本,并且通過 vendor/modules.txt 記錄了 vendor 下的 module 以及版本。

如果我們要基于 vendor 構(gòu)建,而不是基于本地緩存的 Go Module 構(gòu)建,我們需要在 go build 后面加上 -mod=vendor 參數(shù)。在 Go 1.14 及以后版本中,如果 Go 項目的頂層目錄下存在 vendor 目錄,那么 go build 默認也會優(yōu)先基于 vendor構(gòu)建,除非你給 go build 傳入-mod=mod的參數(shù)。

通常我們直接使用 go module (非vendor) 模式即可滿足大部分需求。如果是那種開發(fā)環(huán)境受限,因無法訪問外部代理而無法通過 go 命令自動解決依賴和下載依賴的環(huán)境下,我們通過 vendor 來輔助解決。

六、Go module 常用命令總結(jié)

6.1 常用 go mod命令

常用的go mod命令如下:

go mod download    下載依賴的module到本地cache(默認為$GOPATH/pkg/mod目錄)
go mod edit        編輯go.mod文件
go mod graph       打印模塊依賴圖
go mod init        初始化當前文件夾, 創(chuàng)建go.mod文件
go mod tidy        增加缺少的module,刪除無用的module
go mod vendor      將依賴復制到vendor下
go mod verify      校驗依賴
go mod why         解釋為什么需要依賴

6.2 go get命令

在項目中執(zhí)行g(shù)o get命令可以下載依賴包,并且還可以指定下載的版本。

  • 運行g(shù)o get -u將會升級到最新的次要版本或者修訂版本(x.y.z, z是修訂版本號, y是次要版本號)
  • 運行g(shù)o get -u=patch將會升級到最新的修訂版本
  • 運行g(shù)o get package@version將會升級到指定的版本號version

如果下載所有依賴可以使用go mod download命令。

6.3 go mod edit

6.3.1 格式化

因為我們可以手動修改go.mod文件,所以有些時候需要格式化該文件。Go提供了一下命令:

go mod edit -fmt

6.3.2 添加依賴項

go mod edit -require=golang.org/x/text

6.3.3 移除依賴項

如果只是想修改go.mod文件中的內(nèi)容,那么可以運行go mod edit -droprequire=package path,比如要在go.mod中移除golang.org/x/text包,可以使用如下命令:

go mod edit -droprequire=golang.org/x/text

關(guān)于go mod edit的更多用法可以通過go help mod edit查看。

七、Go Module 代理

7.1 GO 設置代理

7.1.1 打開模塊支持

go env -w GO111MODULE=on

7.1.2 取消代理

go env -w GOPROXY=direct

7.1.3 關(guān)閉包的有效性驗證

go env -w GOSUMDB=off

7.1.4 設置不走 proxy 的私有倉庫或組,多個用逗號相隔(可選)

go env -w GOPRIVATE=git.mycompany.com,github.com/my/private

7.1.5 國內(nèi)常用代理列表

提供者地址
官方全球代理https://proxy.golang.com.cn
七牛云https://goproxy.cn
阿里云https://mirrors.aliyun.com/goproxy/
GoCenterhttps://gocenter.io
百度https://goproxy.bj.bcebos.com/

“direct” 為特殊指示符,用于指示 Go 回源到模塊版本的源地址去抓取(比如 GitHub 等),當值列表中上一個 Go module proxy 返回 404 或 410 錯誤時,Go 自動嘗試列表中的下一個,遇見 “direct” 時回源,遇見 EOF 時終止并拋出類似 “invalid version: unknown revision…” 的錯誤。

7.1.6 官方全球代理

go env -w GOPROXY=https://proxy.golang.com.cn,direct
go env -w GOPROXY=https://goproxy.io,direct
go env -w GOSUMDB=gosum.io+ce6e7565+AY5qEHUk/qmHc5btzW45JVoENfazw8LielDsaI+lEbq6
go env -w GOSUMDB=sum.golang.google.cn

七牛云

go env -w GOPROXY=https://goproxy.cn,direct
go env -w GOSUMDB=goproxy.cn/sumdb/sum.golang.org

阿里云

go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
# GOSUMDB 不支持

GoCenter

go env -w GOPROXY=https://gocenter.io,direct
# 不支持 GOSUMDB

百度

go env -w GOPROXY=https://goproxy.bj.bcebos.com/,direct
# 不支持 GOSUMDB

Goland設置代理

八.項目中使用Go module

8.1 既有項目

如果需要對一個已經(jīng)存在的項目啟用go module,可以按照以下步驟操作:

  • 在項目目錄下執(zhí)行g(shù)o mod init,生成一個go.mod文件。
  • 執(zhí)行g(shù)o get,查找并記錄當前項目的依賴,同時生成一個go.sum記錄每個依賴庫的版本和哈希值。

8.2 新項目

對于一個新創(chuàng)建的項目,我們可以在項目文件夾下按照以下步驟操作:

  • 執(zhí)行g(shù)o mod init 項目名命令,在當前項目文件夾下創(chuàng)建一個go.mod文件。
  • 手動編輯go.mod中的require依賴項或執(zhí)行g(shù)o get自動發(fā)現(xiàn)、維護依賴。

九、查看Go的配置

$ go env
//以JSON格式輸出
$ go env -json

以上就是淺析Go項目中的依賴包管理與Go Module常規(guī)操作的詳細內(nèi)容,更多關(guān)于go Module的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 為什么GO不支持循環(huán)引用

    為什么GO不支持循環(huán)引用

    這篇文章主要介紹的是為什么GO不支持循環(huán)引用,學習 Go 語言的開發(fā)者越來越多了,很多小伙伴在使用時,就會遇到種種不理解的問題,其中一點就是包的循環(huán)引用的報錯,下main文章我們一起來看看學習原因
    2021-10-10
  • golang網(wǎng)絡socket粘包問題的解決方法

    golang網(wǎng)絡socket粘包問題的解決方法

    這篇文章主要介紹了golang網(wǎng)絡socket粘包問題的解決方法,簡單講述了socket粘包的定義并結(jié)合實例形式分析了Go語言解決粘包問題的方法,需要的朋友可以參考下
    2016-07-07
  • Golang實現(xiàn)按比例切分流量的示例詳解

    Golang實現(xiàn)按比例切分流量的示例詳解

    我們在進行灰度發(fā)布時,往往需要轉(zhuǎn)發(fā)一部分流量到新上線的服務上,進行小規(guī)模的驗證,隨著功能的不斷完善,我們也會逐漸增加轉(zhuǎn)發(fā)的流量,這就需要按比例去切分流量,那么如何實現(xiàn)流量切分呢,接下來小編就給大家詳細的介紹一下實現(xiàn)方法,需要的朋友可以參考下
    2023-09-09
  • go語言調(diào)用其他包中的函數(shù)簡單示例

    go語言調(diào)用其他包中的函數(shù)簡單示例

    這篇文章主要給大家介紹了關(guān)于go語言調(diào)用其他包中的函數(shù)的相關(guān)資料,文中還介紹了Go語言同一個包中不同文件之間函數(shù)調(diào)用的相關(guān)問題,需要的朋友可以參考下
    2023-01-01
  • Go學習筆記之map的聲明和初始化

    Go學習筆記之map的聲明和初始化

    map底層是由哈希表實現(xiàn)的,Go使用鏈地址法來解決鍵沖突,下面這篇文章主要給大家介紹了關(guān)于Go學習筆記之map的聲明和初始化的相關(guān)資料,需要的朋友可以參考下
    2022-11-11
  • go語言接口的定義和實現(xiàn)簡單分享

    go語言接口的定義和實現(xiàn)簡單分享

    這篇文章主要介紹了go語言接口的定義和實現(xiàn)簡單分享的相關(guān)資料,需要的朋友可以參考下
    2023-08-08
  • Go返回int64類型字段超出javascript Number范圍的解決方法

    Go返回int64類型字段超出javascript Number范圍的解決方法

    這篇文章主要介紹了Go返回int64類型字段超出javascript Number范圍的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-07-07
  • go單體日志采集zincsearch方案實現(xiàn)

    go單體日志采集zincsearch方案實現(xiàn)

    這篇文章主要為大家介紹了go單體日志采集zincsearch方案實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-07-07
  • Go語言在Linux環(huán)境下輸出彩色字符的方法

    Go語言在Linux環(huán)境下輸出彩色字符的方法

    這篇文章主要介紹了Go語言在Linux環(huán)境下輸出彩色字符的方法,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-04-04
  • golang爬蟲colly?發(fā)送post請求

    golang爬蟲colly?發(fā)送post請求

    本文主要介紹了golang爬蟲colly?發(fā)送post請求實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-07-07

最新評論