跳至主要内容

[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 可以設定為 onoff,在新版本的 Go 中幾乎都是使用 on 來啟用 Go Modules。

go env

在這些環境變數中,比較重要的有以下幾個 :

  • GOROOT : Go 的安裝的根目錄,當 import 內建的 package 時會從這個目錄中的 src 目錄中尋找
  • GOPATH : Go 的 workspace,所有的專案都會放在這個目錄下,通常需要有 srcbinpkg 這三個目錄,是在 Go Modules 出現之前的依賴管理方式。在 Go Modules 出現之後,GOPATH 就不再重要
  • GOBIN : Go 的執行檔目錄,當我們使用 go install 時會將執行檔放在這個目錄下,如果沒有設定的話會放在 GOPATH/bin 下,通常應該加到環境變數中
  • GOMODCACHE : Go Modules 的快取目錄,預設是在 GOPATH 下的 pkg/mod 目錄,用來存放下載的 module
  • GOPROXY : 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 檔案,內容類似下面這樣 :

go.mod
module github.com/RulerChen/go-demo

go 1.23.4

module 的名稱就是我們剛剛輸入的名稱,go 1.23.4 是 Go 的版本號,如果執行的時候低於這個版本的話會報錯。

接著我們在專案中新增一個 main.go 檔案,內容如下 :

main.go
package main

import "fmt"

func main() {
fmt.Println("Hello World")
}

並輸入 go run main.go 來執行程式,可以看到輸出 Hello World

Demo 2 : 引入 package

接著我們稍微讓專案變得複雜一點,來示範 Go Modules 的使用。

file tree
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,實際上命名應該跟目錄名稱一樣。

utils/utils.go
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)
}
main.go
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 檔案中多了幾行 :

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 不會被竄改。

go.sum
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