[Go] 依賴管理
本文基於 Go 1.23.4 版本,介紹 Go 的依賴管理方式
名詞解釋
- repository : 一個 git 或者其他版本控制系統的專案
- module : 一個 Go 專案,包含一個
go.mod
檔案 - package : 一個目錄下的 一個或多個
.go
檔案,會在最上方加上package
來定義
Go 與 Node.js 等等的語言對於 module 和 package 的定義有所不同,Go 的 module 是指一個專案,而 package 是指一個目錄下的一個或多個 .go
檔案,而 Node.js 則是下載 package 並引入其中的 module。
Go Env
Go 1.11 之後,Go 引入了 Go Modules 來管理依賴,可以透過 go env
來查看 Go 的環境變數,其中有一個 GO111MODULE
可以設定為 on
、off
,在新版本的 Go 中幾乎都是使用 on
來啟用 Go Modules。
go env
在這些環境變數中,比較重要的有以下幾個 :
GOROOT
: Go 的安裝的根目錄,當 import 內建的 package 時會從這個目錄中的src
目錄中尋找GOPATH
: Go 的 workspace,所有的專案都會放在這個目錄下,通常需要有src
、bin
、pkg
這三個目錄,是在 Go Modules 出現之前的依賴管理方式。在 Go Modules 出現之後,GOPATH
就不再重要GOBIN
: Go 的執行檔目錄,當我們使用go install
時會將執行檔放在這個目錄下,如果沒有設定的話會放在GOPATH/bin
下,通常應該加到環境變數中GOMODCACHE
: Go Modules 的快取目錄,預設是在GOPATH
下的pkg/mod
目錄,用來存放下載的 moduleGOPROXY
: Go Modules 的代理伺服器,預設是https://proxy.golang.org,direct
,代表會先前往https://proxy.golang.org
下載,如果沒有的話才會連結到原始的 repository 下載GOPRIVATE
: 用來設定私有的 repository,可以透過這個環境變數來設定不透過代理伺服器下載的 repository,像是GOPRIVATE=github.com/myorg/*
代表github.com/myorg
下的 repository 都不透過代理伺服器下載
Demo
Demo 1 : Hello World
首先我們執行 go mod init
來初始化一個 Go 專案,後面跟著的參數是 module 的名稱,通常是 github.com/<yourname>/<yourproject>
,這樣可以確保 module 的唯一性,並且方便其他人引用。
go mod init github.com/<yourname>/<yourproject>
# go mod init github.com/RulerChen/go-demo
執行之後就會產生一個 go.mod
檔案,內容類似下面這樣 :
module github.com/RulerChen/go-demo
go 1.23.4
module 的名稱就是我們剛剛輸入的名稱,go 1.23.4
是 Go 的版本號,如果執行的時候低於這個版本的話會報錯。
接著我們在專案中新增一個 main.go
檔案,內容如下 :
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
並輸入 go run main.go
來執行程式,可以看到輸出 Hello World
。
Demo 2 : 引入 package
接著我們稍微讓專案變得複雜一點,來示範 Go Modules 的使用。
go-demo/
├── go.mod
├── main.go
└── utils/
└── utils.go
在下方的範例中,我們要特別注意的是 import
的部分,我們引入的是 [module_path]/[package_path]
,而不是 [module_path]/[package_name]
,我們在 utils.go
中宣告的是 package util
,所以在 main.go
中引入的是 github.com/Rulerchen/go-demo/utils
,所以在調用時是 util.Reverse
而不是 utils.Reverse
。
這邊是為了示範需要才將 package 命名為 util
,實際上命名應該跟目錄名稱一樣。
package util
import "strings"
func Reverse(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
func ToUpper(s string) string {
return strings.ToUpper(s)
}
package main
import (
"fmt"
util "github.com/RulerChen/go-demo/utils"
"github.com/fatih/color"
)
func main() {
color.Cyan("=== Go Modules Demo ===")
original := "Hello, Go Modules!"
reversed := util.Reverse(original)
upper := util.ToUpper(original)
fmt.Println("Original string:", original)
fmt.Println("Reversed string:", reversed)
fmt.Println("Uppercased string:", upper)
}
除此之外,也有以下幾種 import 的方式 :
import (
// 使用 package 的名稱
"fmt"
"github.com/RulerChen/go-demo/utils"
// 使用 package 的別名
util "github.com/RulerChen/go-demo/utils"
// 把所有的 function 都引入,這樣就不需要使用 util.Reverse,直接使用 Reverse 就可以了 (不建議)
. "github.com/RulerChen/go-demo/utils"
// 不使用 package,但是會執行 package 的 init 函式
_ "github.com/fatih/color"
)
由於我們引用了第三方的 package github.com/fatih/color
,所以我們需要執行 go get
來下載這個 package,這個指令會將 package 下載並將這個 package 加入到 go.mod
中。
go get github.com/fatih/color
之後我們可以看到我們的 go.mod
檔案中多了幾行 :
module github.com/RulerChen/go-demo
go 1.23.4
require (
github.com/fatih/color v1.18.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.25.0 // indirect
)
在 go.mod
中,有許多常見的 keywords :
module
: module 的名稱go
: Go 的版本require
: 這個 module 的依賴,後面會跟著 package 的名稱和版本- 第一個
require
是直接依賴的 package - 第二個
require
後面的// indirect
代表這個 package 是間接依賴的 package,是透過直接依賴的 package 引入的
- 第一個
exclude
: 明確排除某個 package 的版本replace
: 用來替換依賴的 package,通常是用來測試或者是開發時使用
同時也新增了 go.sum
檔案,這個檔案是用來記錄下載的 package 的版本以及經過 SHA-256 加密的 hash 值,用來確保下載的 package 不會被竄改。
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
接著執行 go run main.go
,可以看到輸出了。
Go Commands
Go 有許多的指令可以用來管理 Go Modules,以下是一些常見的指令 :
-
go run
: 執行程式 -
go run main.go utils/utils.go
: 執行多個檔案 -
go build
: 編譯程式並產生執行檔,但是不會執行 -
go install
: 編譯程式並將執行檔放在GOBIN
中 -
go get github.com/fatih/color
: 下載 package -
go get -u github.com/fatih/color
: 更新 minor 版本的 package -
go get github.com/fatih/color@none
: 移除 package -
go mod init
: 初始化一個 Go 專案 -
go mod tidy
: 整理go.mod
檔案,刪除沒有使用到的 package 並安裝缺少的 package -
go mod download
: 下載所有的依賴到GOMODCACHE
中 -
go mod verify
: 驗證go.sum
檔案中的 hash 值是否正確 -
go clean -modcache
: 清除GOMODCACHE
中的 package