模組入門


Go 在 1.11 時內建了實驗性的模組管理功能,並藉由 GO111MODULE 來決定是否啟用,可設定的值是 auto(1.11 ~ 1.13 預設)、onoff

若使用 Go 1.13,當設定值是 auto,執行建構指令時,會看看是否有個 go.mod 檔案(用來定義依賴的模組),若有就使用 Go 模組功能,如果沒有 go.mod 檔案,就採用舊式 GOPATH、vendor 的特性。

當設定值為 on 時,就是始終使用 Go 模組功能(從 1.12 之後,go.mod 可以在必要時再新增),模組下載後會自動安裝至 GOPATH

go.mod 不可以在 GOPATH 之中。

設定值為 off 時就是不使用 Go 模組功能。

例如,現在有個 pkgfoo 釋出了 v1.0.0 版,而你打算基於它寫個 go-exercise,go-exercise 資料夾中有個 src/main/main.go:

package main

import "github.com/JustinSDK/pkgfoo"

func main() {
    kgfoo.Hi()
    pkgfoo.Hello()
}

現在進入你的 go-exercise 資料夾底下,執行 go mod init go-exercise,這會建立一個 go.mod,使用 Go 1.13 的話,預設內容是:

module go-exercise

go 1.13

從 Go 1.12 開始,預設的 go.mod 中會有版本字段,放置了 go.mod 的資料夾稱為模組根(module root)目錄,通常就是一個 repository 的根目錄,該目錄下的全部套件都屬於該模組(除了那些本身包含 go.mod 檔案的子目錄之外)。

接著執行 go build -o bin/main.exe src/main/main.go,這時會自動找出 import 陳述,執行了套件的下載並完成建構,此時會顯示以下訊息:

go: finding github.com/JustinSDK/pkgfoo v1.0.0
go: downloading github.com/JustinSDK/pkgfoo v1.0.0
go: extracting github.com/JustinSDK/pkgfoo v1.0.0

而 go.mod 也有了底下內容:

module exercise

go 1.13

require github.com/JustinSDK/pkgfoo v1.0.0

go.mod 定義了相依的套件與版本,若是第一次執行 go build,那麼總是會下載最新版本,你也可以自行編輯 go.mod 的內容,來取得想要的版本,另外你也會發現多了個 go.sum,其中包含了套件的 hash 等訊息,這用來確認取得的是正確的版本:

github.com/JustinSDK/pkgfoo v1.0.0 h1:XOi67njsT9pcRrsT40Oi3LCA3b1TyIxHd6+9ceGwa0U=
github.com/JustinSDK/pkgfoo v1.0.0/go.mod h1:5PAHGmqvfj2XbzxxOeiJJjOflE/p6zTVRFfaiEeSn1w=

接著在執行建構出來的可執行檔時會看到:

Hi
Helo

喔!Hello 少了一個小寫的 l,這是一個小 bug,在修正之後,發佈了 v1.0.1

現在 appfoo 為了要能取得更新,可以使用 go get -u,這會昇級到最新的 MINOR 或 PATCH 版本,像是從 1.0.0 到 1.0.1,或者是 1.0.0 到 1.1.0,是的,這採用的是 Semantic Versioning;若是使用 go get -u=patch all,會將用到的套件昇級至最新的 PATCH 版本,像是從 1.0.0 到 1.0.1;若是使用 go get package@version,可以指定昇級至某個版本號,例如 go get github.com/JustinSDK/pkgfoo@v1.0.1,然而,不建議以此方式昇級至新的 MAJOR 版本,原因後述。

在這邊因為只是小 bug 更新,就使用 go get -u=patch all,這會看到底下的訊息:

go: finding github.com/JustinSDK/pkgfoo v1.0.1
go: downloading github.com/JustinSDK/pkgfoo v1.0.1
go: extracting github.com/JustinSDK/pkgfoo v1.0.1

go.mod 的內容也更新了(go.sum 也會更新):

module go-exercise

go 1.13

require github.com/JustinSDK/pkgfoo v1.0.1

重新執行 go build,就會顯示正確的訊息了:

Hi
Hello

假設現在 pkgfoo 中的訊息都改成中文,並更新為 v2.0.0 了,雖然可以使用 go get github.com/JustinSDK/pkgfoo@v2.0.0 來下載最新版本,然而會出現 +incompatible 字樣:

go: finding github.com/JustinSDK/pkgfoo v2.0.0
go: downloading github.com/JustinSDK/pkgfoo v2.0.0+incompatible

雖然可以順利建構,執行時也會是最新版本的結果,然而,若要昇級至最新的 MAJOR 版本,依賴的套件,必須明確地屬於某個模組,因此,pkgfoo 中必須有個 go.mod,並定義版本資訊:

module github.com/JustinSDK/pkgfoo/v2

go.mod 在加入了 pkgfoo 之後,發佈了 v2.0.0 ,現在 appfoo 打算使用這 v2.0.0,可以在 import 時指定:

package main

import "github.com/JustinSDK/pkgfoo/v2"

func main() {
    pkgfoo.Hi()
    pkgfoo.Hello()
}

直接 go build -o bin/main.exe src/main/main.go,就會看到下載了 v2.0.0:

go: finding github.com/JustinSDK/pkgfoo/v2 v2.0.0
go: downloading github.com/JustinSDK/pkgfoo/v2 v2.0.0
go: extracting github.com/JustinSDK/pkgfoo/v2 v2.0.0

而且你可以看到 appfoo 的 go.mod 更新為:

module go-exercise

go 1.13

require (
    github.com/JustinSDK/pkgfoo v1.0.1
    github.com/JustinSDK/pkgfoo/v2 v2.0.0
)

現在它依賴在…兩個版本?是的,事實上,你也可以同時在 appfoo 中使用:

package main

import "github.com/JustinSDK/pkgfoo/v2"
import pkgfooV1 "github.com/JustinSDK/pkgfoo"

func main() {
    pkgfoo.Hi()
    pkgfoo.Hello()
    pkgfooV1.Hi()
    pkgfooV1.Hello()        
}

不同模組版本的套件,被視為不同的套件,上面的程式執行過時會顯示:

嗨
哈囉
Hi
Hello