Go 部落格

發佈 Go 模組

Tyler Bui-Palsulich
2019 年 9 月 26 日

簡介

這篇文章是分篇連載的第 3 篇。

注意:如需開發模組的文件,請參閱 開發並發佈模組

此文將討論如何撰寫及發佈模組,讓其他模組可依賴它們。

請注意:這篇文章報導的開發版本包含 v1。如果您有興趣瞭解 v2,請參閱 Go 模組:第 2 版及後續版本

本篇文章範例中使用 Git,也支援 MercurialBazaar 及其他版本控制軟體。

專案設定

在看這篇文章時,您需要一個現有的專案做為範例。因此,可使用 使用 Go 模組 文章最後面的檔案

$ cat go.mod
module example.com/hello

go 1.12

require rsc.io/quote/v3 v3.1.0

$ cat go.sum
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

$ cat hello.go
package hello

import "rsc.io/quote/v3"

func Hello() string {
    return quote.HelloV3()
}

func Proverb() string {
    return quote.Concurrency()
}

$ cat hello_test.go
package hello

import (
    "testing"
)

func TestHello(t *testing.T) {
    want := "Hello, world."
    if got := Hello(); got != want {
        t.Errorf("Hello() = %q, want %q", got, want)
    }
}

func TestProverb(t *testing.T) {
    want := "Concurrency is not parallelism."
    if got := Proverb(); got != want {
        t.Errorf("Proverb() = %q, want %q", got, want)
    }
}

$

接著,建立一個新的 git 存放庫並新增初始提交。如果您要發佈自己的專案,請務必包含一個 LICENSE 檔案。變更到含有 go.mod 的目錄,然後建立存放庫

$ git init
$ git add LICENSE go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: initial commit"
$

語意化版本與模組

go.mod 中的每個必備模組都有 語意化版本,這個版本是建置模組所使用的相依項目的最低版本。

語意化版本的形式為 vMAJOR.MINOR.PATCH

  • 當您對模組的公開 API 進行 向後不相容 的變更時,請增加 MAJOR 版本。這只有在絕對有必要時才應該執行。
  • 當您對 API 進行向後相容的變更,例如變更相依項目或新增新的函式、方法、結構欄位或類型時,請增加 MINOR 版本。
  • 在進行不影響模組的公開 API 或相依項目的次要變更,例如修正錯誤後,請增加 PATCH 版本。

您可以透過附加連字號和以句點分隔的識別碼 (例如 v1.0.1-alphav2.2.2-beta.2) 來指定預發行版本。go 指令比較喜歡一般發行版本,而不是預發行版本,因此如果您的模組有任何一般發行版本,使用者必須明確要求預發行版本 (例如 go get example.com/hello@v1.0.1-alpha)。

v0 主要版本與預發行版本不保證向後相容性。在對使用者做出穩定性承諾之前,您可以用它們來改善您的 API。不過,v1 主要版本及更高版本則需要在那一個主要版本內具有向後相容性。

go.mod 中所引用的版本可能是存放庫中明確標記的發行版本 (例如 v1.5.2),或基於特定提交的 擬版本 (例如 v0.0.0-20170915032832-14c0d48ead0c)。擬版本是預發行版本的特例。當使用者需要依賴尚未發佈任何語意化版本標記的專案,或針對尚未標記的提交進行開發時,擬版本會很有用,但使用者不應假設擬版本會提供穩定或測試完善的 API。以明確版本標記您的模組會向您的使用者發出訊號,表示特定版本已充分測試,而且已經準備好使用。

一旦您開始以版本標記儲存庫,在開發模組的過程中,持續標記新的發行版本非常重要。當使用者要求模組的新版本 (使用 go get -ugo get example.com/hello) 時,go 指令會選擇可用的最大語意化發行版本,即使該版本已有好幾年歷史,而且許多變更都落後於主要分支。持續標記新的發行版本會讓您的使用者可用於持續改進的功能。

不要從儲存庫中刪除版本標籤。如果您發現某個版本的錯誤或安全性問題,請發布一個新版本。如果其他人依賴您已刪除的版本,它們的建置可能會失敗。同樣地,一旦您發布一個版本,就不要變更或覆寫它。模組鏡像和雜湊資料庫會儲存模組、模組的版本和帶有簽名的加密雜湊,以確保特定版本可以長期重複建置。

v0:最初的不穩定版本

讓我們使用語義化版本 v0標記這個模組。v0版本無法保證穩定性,所以幾乎所有專案都應從v0開始,同時逐漸完善公開 API。

標記新版本有幾個步驟

  1. 執行 go mod tidy,移除模組中任何可能已累積但不再必要的相依性。

  2. 最後一次執行 go test ./...,確保一切正常。

  3. 使用 git tag標記專案,使用新版本。

  4. 將新的標籤推送到起源儲存庫。

$ go mod tidy
$ go test ./...
ok      example.com/hello       0.015s
$ git add go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: changes for v0.1.0"
$ git tag v0.1.0
$ git push origin v0.1.0
$

現在其他專案可以依賴於example.com/hellov0.1.0。對於您自己的模組,您可以執行 go list -m example.com/hello@v0.1.0,確認可以使用最新版本(這個模組範例不存在,所以無法取得任何版本)。如果您沒有立即看到最新版本,而且您使用 Go 模組代理(從 Go 1.13 開始的預設值),請在幾分鐘後再試一次,讓代理伺服器有時間載入新版本。

如果您新增到公共 API,對v0模組進行重大變更,或是升級依賴項的次要或版本,請為下一個版本增加次要版本。例如,v0.1.0之後的下一個版本將會是v0.2.0

如果您修正先前版本的錯誤,請增加修補版本。例如,v0.1.0之後的下一個版本將會是v0.1.1

v1:第一個穩定版本

當您對模組的 API 絕對有把握後,可以發布v1.0.0。主版本號v1傳達給使用者模組的 API 將不會進行不相容變更。他們可以升級到新的v1次要版本和修補版本,而他們的程式碼不應會中斷。函式和方法簽名不會變更,匯出的類型不會被移除,依此類推。如果有 API 變更,它們將會向下相容(例如,新增欄位到結構),並包含在新次要版本中。如果有些錯誤修正(例如,安全性修正),它們將包含在修補版本中(或包含在次要版本中)。

有時候,維護向後相容性可能會造成不順手的 API。這很正常。不完美的 API 也優於破壞使用者的現有程式碼。

標準程式庫的 strings 套件就是為了維持向後相容性以成本 API 一致性而有的絕佳範例。

  • Split 會將字串切片為所有由分隔符號分隔的子字串,並傳回這些分隔符號之間的子字串切片。
  • SplitN 可用來控制要傳回的子字串數目。

不過,Replace 計算的是要從頭開始取代的字串有幾個執行個體(不像 Split)。

根據 SplitSplitN,您會預期看到像 ReplaceReplaceN 這樣的函式。但是,在不破壞呼叫方(我們承諾不這麼做)的情況下,我們並無法變更現有的 Replace。因此,在 Go 1.12 中,我們新增了新函式,ReplaceAll。產生的 API 稍嫌奇怪,因為 SplitReplace 的行為不同,但這種不一致的性優於強制變更。

假設您滿意 example.com/hello 的 API,並且想要將 v1 發布為第一個穩定版本。

加上 v1 標籤所使用的程序與加上 v0 版本標籤的程序相同:執行 go mod tidygo test ./...,加上版本標籤,並將標籤推送到原始程式庫

$ go mod tidy
$ go test ./...
ok      example.com/hello       0.015s
$ git add go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: changes for v1.0.0"
$ git tag v1.0.0
$ git push origin v1.0.0
$

此時,example.com/hellov1 API 已確定。這傳達給大家的訊息是我們的 API 穩定,他們可以放心地使用。

結論

本文詳細說明加上語意化版本模組標籤的程序,以及何時要發布 v1。後續的文章將說明如何在 v2 及其後續版本維護和發布模組。

為了提供回饋並協助做好 Go 相依性管理的未來規劃,請寄送 錯誤報告經驗報告 給我們。

感謝您提供的所有回饋和協助改進 Go 模組。

下一篇文章: 使用 Go 1.13 中的錯誤
前一篇文章: Go 1.13 已發布
部落格索引