從一知半解到略懂 Go modules
Posted on Nov 2, 2020 in Go 程式設計 - 初階 by Amo Chen ‐ 3 min read
一直以來沒有好好去詳讀 go modules 的文件,所以都覺得對 go modules 只是一知半解。這次花了些時間看了關於 go modules 的相關文件,並實際寫個小範例體驗,最後整理成本文分享。
本文環境
- macOS 10.15
- Go 1.13
引言
先前 Golang - 從 Hello World 認識 GOPATH 一文中,我們認識了 GOPATH 的作用,然而 GOPATH 會讓我們的專案程式碼與其他相依的程式碼一起存在 $GOPATH/src
資料夾底下,相較於其他程式語言而言,使用上較不直覺,也欠缺相依性管理的功能。
Go 1.11 之後提供 go modules 讓我們可以不需要把專案程式碼放在 $GOPATH/src
中開發,此外還能管理套件相依性,相當便利。
Go modules 初體驗
首先設置好 GOPATH 之後,先在 $GOPATH/src
之外,新增 1 個資料夾存放專案程式:
$ export GOPATH=/path/to/goworkspace
$ mkdir myproject
$ cd myproject
接著用以下指令新增 Go module:
$ go mod init github.com/username/myproject
p.s. github.com/username/myproject
可以換成任意字串,因爲個人希望將 Go module 放置於 GitHub, 因此將模組名稱設定為 github.com/username/myproject
上述指令成功後,將會看到資料夾內出現 1 個檔案 go.mod
:
module github.com/username/myproject
go 1.13
go.mod
用來紀錄 Go module 的名稱與所使用的 Go 版本,以及相依的 Go modules, 該檔案是 Go module 必備的檔案
再來新增 2 個資料夾,以及 2 個 .go
檔,建立範例所需要的環境:
$ mkdir greeting cli
$ touch greeting/greeting.go cli/say.go
進行至此, myproject
的資料夾結構應如下所示:
.
├── cli
│ └── say.go
├── go.mod
└── greeting
└── greeting.go
最後將 greeting.go
與 say.go
填入以下程式碼。 greeting.go
是 1 個簡單的 package, 用以列印所傳入的字串;而 say.go
則是用以呼叫 greeting.go
package 所提供的函示。
greeting.go
的內容:
package greeting
import "fmt"
func Say(s string) {
fmt.Println(s)
}
say.go
的內容:
package main
import "github.com/username/myproject/greeting"
func main() {
greeting.Say("Hello")
}
最後,試著編譯一次,正常的話不會有任何錯誤訊息:
$ go build ./...
至此,我們已經利用 go modules 成功地將 Go 專案移出 $GOPATH/src
囉!
p.s. 如果把 go.mod
刪除的話,就會發現類似以下的錯誤,這是由於 go 找不到 go.mod
因此轉而至 $GOPATH
尋找相關的 go package 的緣故:
cli/say.go:3:8: cannot find package "github.com/username/myproject/greeting" in any of:
/usr/local/go/src/github.com/username/myproject/greeting (from $GOROOT)
$GOPATH/src/github.com/username/myproject/greeting (from $GOPATH)
使用 go modules 進行套件相依性管理
Go modules 提供的另一個方便的功能則是套件相依性管理,接下來實際透過以下指令安裝套件試試:
$ go get github.com/fatih/color
安裝成功之後,可以再看一次 go.mod
,會發現多了 1 行 require github.com/fatih/color v1.9.0
:
module github.com/username/myproject
go 1.13
require github.com/fatih/color v1.9.0
require github.com/fatih/color v1.9.0
目前的 Go 專案需要 v1.9.0 版的 github.com/fatih/color
。
p.s. go modules 使用的版本號規則是semantic version , 有興趣的話,可以詳閱該文件
有時候我們可能會需要使用指定版本的 package, 這時候可以在 package 尾端加上 @版本號
,例如以下指定使用 v1.8.0 的 github.com/fatih/color
:
$ go get github.com/fatih/[email protected]
安裝完成後,再看一次 go.mod
會發現除了 github.com/fatih/color
版本變為 v1.8.0 之外,又多了 2 個 //indirect
的 go packages:
module github.com/username/myproject
go 1.13
require (
github.com/fatih/color v1.8.0
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.11 // indirect
)
//indirect
指的是被相依的套件所使用的 packages:
The indirect comment indicates a dependency is not used directly by this module, only indirectly by other module dependencies.
另一種常見情況是我們可能會指定 package 到某個 commit id, 這時候就能夠使用 pseudo-version ,例如 v0.0.0-20170915032832-14c0d48ead0c
就是 1 個指定使用 20170915032832-14c0d48ead0c
commit 的 pseudo-version.
pseudo-version , which is the go command’s version syntax for a specific untagged commit.
接著,可以再把 greeting.go
與 say.go
改為以下形式,使用剛剛所安裝的 package 。
greeting.go
的內容:
package greeting
import "fmt"
import "github.com/fatih/color"
func Say(s string) {
fmt.Println(s)
}
func SayWithColor(s string) {
color.Red(s)
}
say.go
的內容:
package main
import "github.com/username/myproject/greeting"
func main() {
greeting.Say("Hello")
greeting.SayWithColor("World")
}
go.mod
的 replace 語法
go.mod
還提供 replace
語法,能夠讓我們取代指定的套件,例如 replace github.com/fatih/color => ../mycolor
代表至 ../mycolor
資料夾中載入 github.com/fatih/color
package, 例如以下的 go.mod
:
module github.com/username/myproject
go 1.13
require (
github.com/fatih/color v1.8.0
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.11 // indirect
)
replace github.com/fatih/color => ../mycolor
除了直接編輯 go.mod
之外,也可以用以下指令:
$ go mod edit -replace github.com/fatih/color=../mycolor
replace
能夠讓我們輕易地將特定 package 重新定位到特定路徑下,除了能夠方便修改之外,也能夠讓我們更輕鬆地測試 package 不同版本的行為等等,值得注意的是特定路徑下的 package 也必須有 go.mod
檔才行
../mycolor
是代表在 go.mod
檔案的所在目錄的上一層,所以可以先切換至上一層目錄後,再次下載 https://github.com/fatih/color
試試:
$ cd ../
$ git clone https://github.com/fatih/color mycolor
此時的資料夾結構應該會類似以下:
.
├── mycolor
│ ├── LICENSE.md
│ ├── README.md
│ ├── color.go
│ ├── color_test.go
│ ├── doc.go
│ ├── go.mod
│ ├── go.sum
│ └── vendor
├── myproject
│ ├── cli
│ ├── go.mod
│ ├── go.sum
│ └── greeting
└── pkg
接著回到 myproject
試著編譯看看,正常的話就不會出現任何訊息:
$ cd myproject
$ go build ./...
如此代表成功體驗 replace 的功用了!
結語
以上就是關於 go modules 的一些解說與用法,還有很多細節可以詳閱官方文件,相信大家閱讀之後都可以有不少收獲!
Happy coding!
References
https://blog.golang.org/using-go-modules
https://golang.org/ref/mod