Go Mod淺談

·

4 min read

Go Mod淺談

為什麼要有依賴管理工具?

依賴管理工具(Dependency management tool)是軟體開發過程中用來管理程式相依套件的工具。在現代軟體開發中,程式往往會使用許多第三方套件來實現其功能,這些套件又可能依賴其他套件,形成了一個相互複雜的依賴關係鏈。因此,依賴管理工具的存在是必要的。

依賴管理關聯模型

DAG(Directed Acyclic Graph)

DAG

像Node的npm就是使用DAG模型來描述程式用到的套件之間的依賴關係.

package.json中間dependencies和devDependencies就是用來描述該專案直接依賴的套件.

{
  "name": "my-project",
  "version": "1.0.0",
  "dependencies": {
    "express": "^4.17.1",
    "lodash": "^4.17.21",
    "moment": "^2.29.1"
  },
  "devDependencies": {
    "mocha": "^9.1.3",
    "chai": "^4.3.4"
  }
}

在這個例子中,專案依賴了三個套件:express、lodash 和 moment。這些套件也可能有它們自己的依賴項目。npm 使用 DAG 來描述這些依賴關係,其中每個套件都是 DAG 中的一個節點,每條有向邊表示一個依賴關係。

例如,假設 express 依賴了 body-parser 和 cookie-parser 這兩個套件,那麼 DAG 就會長成這樣:

          +----------------------+
          |       my-project      |
          +----------------------+
                      |
         +------------+-------------+
         |                          |
+----------------+        +----------------+
|     express    |        |     lodash     |
+----------------+        +----------------+
         |                          |
         +------------+-------------+
                      |
   +------------------+-------------------+
   |                  |                   |
+--------------+   +----------------+  +--------------+
| body-parser  |   | cookie-parser |  | moment       |
+--------------+   +----------------+  +--------------+

在這個 DAG 中,my-project 是根節點,表示專案本身。express、lodash 和 moment 是子節點,表示這些套件是專案的直接依賴項目。body-parser 和 cookie-parser 是孫節點,表示這些套件是 express 的依賴項目。

總之,Node.js 的 npm 使用 DAG 來描述專案的依賴關係,其中每個套件都是 DAG 中的一個節點,每條有向邊表示一個依賴關係。

Go module

Go 1.11推出來的依賴管理工具(略...)

go mod指令

> go help mod  
Go mod provides access to operations on modules.

Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.

Usage:

        go mod <command> [arguments]

The commands are:

        download    download modules to local cache
        edit        edit go.mod from tools or scripts
        graph       print module requirement graph
        init        initialize new module in current directory
        tidy        add missing and remove unused modules
        vendor      make vendored copy of dependencies
        verify      verify dependencies have expected content
        why         explain why packages or modules are needed

Use "go help mod <command>" for more information about a command.

go mod init

它用於初始化一個新的 Go 模組。當你創建一個新的 Go 專案時,你需要先初始化一個模組,以便在專案中管理依賴關係。

go mod init [module-path]

go mod init example.com/myapp

go mod download

它用於下載指定模組的所有依賴關係(等同於npm install)。當你在一個新的環境中或者首次使用某個模組時,你需要下載模組所依賴的所有套件,這時 go mod download 命令就很有用。

go mod download

go mod download 命令還可以用於下載特定版本的套件。例如,如果你要下載 github.com/some/dependency 的 v1.2.3 版本,可以運行以下命令:

go mod download github.com/some/dependency@v1.2.3

這條命令會下載 github.com/some/dependency 的 v1.2.3 版本及其所有依賴關係,並將它們存儲在本地緩存中。

go mod tidy

用於維護模組的依賴關係,並清理掉沒有使用的套件。當你在專案中添加、更新或刪除依賴關係時,可能會產生一些未使用的套件或者遺漏了某些依賴關係,這時 go mod tidy 命令就很有用。

go mod tidy

使用它可以保持你的依賴關係清晰和一致,並幫助你減小專案的體積。

go mod verify

它用於驗證依賴套件的完整性。當你下載並安裝了一個依賴套件時,你可能會擔心它是否已被修改或是否包含惡意代碼,這時 go mod verify 命令就很有用。

go mod verify

這個命令會驗證當前使用的所有依賴套件是否與 go.sum 文件中列出的哈希值相匹配。如果套件的哈希值不匹配,go mod verify 會報告錯誤,表示該套件可能已被修改或包含惡意代碼。

go mod verify 命令還可以驗證特定版本的依賴套件。例如,如果你要驗證 github.com/some/dependency 的 v1.2.3 版本,可以運行以下命令:

go mod verify github.com/some/dependency@v1.2.3

這個命令會驗證 github.com/some/dependency 的 v1.2.3 版本是否與 go.sum 文件中列出的哈希值相匹配。

總之,go mod verify 是一個用於驗證依賴套件完整性的命令,它可以幫助你確定下載的套件是否已被修改或是否包含惡意代碼。

go mod why

它用於查詢依賴套件的原因。當你開發一個專案時,你可能會想知道為什麼你的專案需要使用某個依賴套件,這時 go mod why 命令就很有用。

簡言之, 告訴我們位什麼需要這依賴套件

go mod why <module>

go mod why github.com/gin-gonic/gin

# github.com/gin-gonic/gin
(main module does not need package github.com/gin-gonic/gin)

這個結果表示主模組沒有直接引用 github.com/gin-gonic/gin 套件,但可能是被其他直接依賴套件引用的。

總之,go mod why 是一個用於查詢依賴套件原因的命令,它可以幫助你了解你的專案為什麼需要使用某個依賴套件。

go mod graph

它可以用來列出當前專案的依賴關係圖。使用 go mod graph 命令可以輕鬆地查看專案中所有套件之間的依賴關係。

當你在專案的根目錄下運行 go mod graph 命令時,它會列出所有當前專案所依賴的套件,以及這些套件之間的相互依賴關係。每一行都代表一個套件及其版本號,並且會列出它所直接依賴的其他套件。例如:

go mod graph

github.com/gorilla/mux github.com/gorilla/context@v1.1.1
github.com/gorilla/mux github.com/gorilla/handlers@v1.2.0
github.com/gorilla/mux github.com/gorilla/mux@v1.8.0

這個例子表示 github.com/gorilla/mux 這個套件依賴於 github.com/gorilla/context@v1.1.1github.com/gorilla/handlers@v1.2.0github.com/gorilla/mux@v1.8.0 這三個套件。

能透過graphviz或者modgv

透過modgv

go mod graph | modgv | dot -Tpng -o graph.png

otelsql

仔細看也會發現其依賴結果的呈現也是DAG的形式

go.mod

module example.com/myapp

go 1.17

require (
    github.com/some/dependency v1.2.3
    github.com/another/dependency v0.0.0-20220321142234-abcd12345678
)

第一行是上述提到的go mod init時, 自己定義的 module-path

第二行則是 go directive, 用來指名你的專案程式碼所需要的Go 最低版本。該指令必須是一個有效的 Go 發布版本:一個正整數加上一個點和一個非負整數(例如,1.9、1.14 等)。

最早的目的是支援 Go 2 過渡期間的向後不兼容的語言更改。

但Go 2還沒出來甚至有可能根本不會出來就是了.

預設會是go mod init當下環境的go版本.

所以如果我把自己開發環境的go升級了, 版本比這專案指定的go directive還新, 則沒有問題, 反之則不行.

第三行的 require 用來列出該專案需要的各個依賴套件和它們的版本

第四行 github.com/some/dependency v1.2.3 則是指名了使用v1.2.3的版本

這裡的版本都是使用Semantic Versioning 2.0.0

第五行的 github.com/another/dependency v0.0.0-20220321142234-abcd12345678 這種稱為 pseudo-version , 這是go module為這個依賴產生的一個類似Semantic Versioning 2.0.0 的版本, 但其實不存在; 因為這個套件並沒有正式透過git tag來發布版本, 而go module又需要一個確定的版本, 才建立出這樣一個 pseudo-version .

go.sum

Go再進行依賴管理時, 除了go.mod還有go.sum

go.sum主要是紀錄所有依賴的module的校驗資訊, 以防下載的依賴被惡意竄改過, 提供於安全校驗.

所以go modules主要是根據這些去找到需要的package的.

儲存的格式如下:

<module> <version> <hash>
<module> <version>/go.mod <hash>
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=

上面第二行version後面跟著 /go.mod 則表示hash值對套件的go.mod ;

反之, 則是對套件的.zip文件.

hash值都是以 h1: 開頭的字串, 因為checksum的演算法是用第一版的SHA256;

如果未來有漏洞則可能改成別種演算法, 到時則用h2.

go get

go get用來下載package, 預設是下載最新的 tag 版本, 接著是抓取main分支的最新commit

go get [-d] [-t] [-u] [build flags] [packages]
# 下載所有依賴package
go get ./.. 

# go get 指定package的最新tag
go get golang.org/x/text
go get golang.org/x/text@latest

# go get 指定package的指定tag
go get golang.org/x/text@v0.3.2

# go get 指定package的指定分支的最新commit
go get golang.org/x/text@master

# go get 指定package的指定commit
go get golang.org/x/text@342b2e
argumentdescription
-d只有下載, 但不安裝
-u更新package到最新的MINOR版本或是PATCH版本, 並且go.mod也會更改
go get -u=patch 會升級到最新的PATCH版本
go get package@version 會更新到指定的version

什麼不知道有什麼版本 ?

go list -m

go list -m -versions package_name
go list -m -versions github.com/golang/protobuf

github.com/golang/protobuf v1.0.0 v1.1.0 v1.2.0 v1.3.0 v1.3.1 v1.3.2 v1.3.3 v1.3.4 v1.3.5 v1.4.0-rc.1 v1.4.0-rc.2 v1.4.0-rc.3 v1.4.0-rc.4 v1.4.0 v1.4.1 v1.4.2 v1.4.3 v1.5.0 v1.5.1 v1.5.2 v1.5.3

實際操作發佈版本

using Github action

  1. 寫個簡單套件, Push到Github, 並且Release

  2. 寫個專案, 引用此套件

  3. 套件進小板號, 透過go get -u更新

  4. 套件進大板號, 透過go get -u看是否更新

Reference

Go Module Reference

Semantic Versioning 2.0.0