前言
截止到 go1.13, go 官方推出的包管理工具 go module 已經發布三個版本了,網上也有很多文章介紹如何使用 go module(推薦觀看附錄中Go夜讀的視頻和官方Wiki),但是大部分都是講如何引用別人的 go module 模塊,鮮有提到如何發佈自己的 go module 包的文章。本文將主要介紹如何“優雅”地發佈自己的 go module 模塊。
本文的代碼和命令均在 ubuntu 19.10 中運行,go 的版本為 1.13.5
pkg.go.dev簡介
img
go.dev 是go官方團隊於2019年11月上線的集合go開發資源的網站,包括一些學習課程和一些go的案例,當然最重要的就是提供了go的第三方包的檢索功能。沒錯,他就是用來取代原來的godoc.org的,現在godoc.org上也有提示提醒用戶遷移到pkg.go.dev。在這篇文章中,我們將把go module模塊發佈到pkg.go.dev。
BTW,在我寫這篇文章的時候(2020.02.19),go官方剛好也宣佈了go.dev不久將開源。
發佈第一個版本
這次要發佈的代碼放在github,所以新建一個項目叫how-to-release-go-module, 新增hello.go 文件
為hello.go添加兩個方法和相關注釋
<code>package pkg
import "fmt"
// Hello says hello.
func Hello() {
\tfmt.Println("Hello go mod!")
}
// Bye says bye.
func Bye() {
\tfmt.Println("Bye go mod!")
}/<code>
執行 go mod init 生成go.mod文件
<code>go mod init
go: creating new go.mod: module github.com/YouEclipse/how-to-release-go-module/<code>
內容如下:
<code>module github.com/YouEclipse/how-to-release-go-module
go 1.13/<code>
我們把代碼push 到遠端分支,看起來好像第一個版本就發佈完畢了。我們打開pkg.go.dev搜索一下github.com/YouEclipse/how-to-release-go-module這個包,卻返回未找到這個包。這是為何?
其實在go.dev的about中說的很清楚了,只有通過proxy.golang.org下載包的時候,module才會自動同步到pkg.go.dev。
To add a package or module, simply fetch it from proxy.golang.org.
但是,實際上,proxy.golang.org 國內基本上是無法訪問的,如果我們使用 goproxy.cn,也一樣能夠同步,我沒有研究goproxy的源碼,但是我和goproxy的作者確認過,goproxy的推算行為會用到 proxy.golang.org,所以使用goproxy.cn作為代理也是可行的。
我們可以隨便建一個項目,執行go get -u github.com/YouEclipse/how-to-release-go-module,因為go1.13 go module 已經是默認打開的,所以會默認通過proxy.golang.org拉取。如果不確定是否配置go proxy,可以執行go env和go env -w 命令查看和修改。
<code>go get -u github.com/YouEclipse/how-to-release-go-module
go: finding github.com/YouEclipse/how-to-release-go-module latest
go: downloading github.com/YouEclipse/how-to-release-go-module v0.0.0-20200219150525-4f41ffd1dd8f
go: extracting github.com/YouEclipse/how-to-release-go-module v0.0.0-20200219150525-4f41ffd1dd8f/<code>
當我們成功拉取後,可以在pkg.go.dev再次搜索(具體可能需要等一段時間,大約是十分鐘到半小時的樣子),這時候我們可以看到搜索結果了
看起來似乎我們的第一次發佈大功告成了。我們看看pkg.go.dev包含了的什麼信息:
- 版本號:由go module自動生成
- 發佈時間
- 開源協議
- commit Hash
- tag:latest
- Overview(概覽)
- 包名
- 源碼地址
- README
- Doc: godoc文檔
- Subdirectories: 子目錄
- Versions:已經發布過的版本
- Imports:引用的包
- Imported By: 引用此包的moudule
- Licenses:開源許可證...
到了這裡我們會發現,godoc 和 module的README 都沒有正常顯示,提示 “Doc” not displayed due to license restrictions.和README not displayed due to license restrictions,是說我們的包沒有開源許可證,所以無法顯示。對於這一點,網上有資料提到go官方團隊和他們的律師討論過才做的這個決定,這也是可以理解的。
pkg.go.dev 支持的證書可以在 https://pkg.go.dev/license-policy 查看,我們只要選擇合適的開源協議證書添加到項目中即可(很遺憾,WTFPL並不在支持的開源協議中)。這裡我們選擇Apache2.0協議,添加到項目中,並push 到遠端分支。
在等待一段時間(這裡我等了大約30分鐘)pkg.go.dev 更新後,我們會發現README和doc都可以正常顯示了。這裡生成的doc和godoc.org沒太大的區別,都是基於代碼和註釋生成的,網上也有很多關於生成godoc最佳實踐的文章,這裡不做贅述。
至此,發佈的module包有godoc文檔,有開源許可證,看起來是這麼個樣子,第一個版本至此就算髮布完了。
發佈新版本
其實我們在發佈第一個版本的時候,為了更新license發佈了兩次,但是兩次的版本都是v0.0.0,這麼看起來似乎和go modules版本化的理念背道而馳。go module 實際上是可以通過tag來發布版本的。當我們需要發佈新版本時,對應的,我們需要使用git tag為這個版本打上標籤。假設我們發佈的下個版本是v1.0.0:
<code>git tag v1.0.0/<code>
打上標籤後push到遠端分支,等待一段時間,我們就可以在pkg.go.dev上看到我們發佈的v1.0.0版本了。
(這裡有些奇怪,之前的v0.0.0消失了,不知道什麼原因)
我們執行 go get -u github.com/YouEclipse/how-to-release-go-module 即可獲取最新發布的版本
<code>go get -u github.com/YouEclipse/how-to-release-go-module
go: finding github.com/YouEclipse/how-to-release-go-module v1.0.0
go: downloading github.com/YouEclipse/how-to-release-go-module v1.0.0
go: extracting github.com/YouEclipse/how-to-release-go-module v1.0.0/<code>
發佈breaking changes
在早期沒有go module時,假設我們引用的第三方包的做了breaking changes,API 發生改變,在跑CI或者重新拉取第三方包後,代碼將會編譯失敗。我印象比較深刻的是在2018年左右,go.uuid的API發生了breaking changes,將原來沒有返回err的函數返回了err,而當時我們沒有任何包管理,都是在docker 鏡像更新時通過go get 拉取,這就導致當時我們的CI都跑失敗了。
go官方其實有關於版本控制的最佳實踐,叫做 Semantic Import Version https://github.com/golang/go/wiki/Modules,即語義化版本。關於語義化版本的說明,附錄中官方的Wiki也有介紹,這裡我們按照官方的最佳實踐執行。這裡強調一下Go 官方對於語義化版本的一個基本原則:
If an old package and a new package have the same import path, the new package must be backwards compatible with the old package."
如果舊軟件包和新軟件包具有相同的導入路徑,則新軟件包必須與舊軟件包向後兼容
所以Go官方給了兩個方案,針對進行大版本升級和breaking changes:
- Major branch 即通過創建version分支和tag進行版本升級
- Major subdirectory 即通過創建version子目錄來區分不同版本
這裡我們只使用Major branch方案來發布,因為第二種方案看起來很奇怪,而且似乎背離了VCS的意義,所有的版本代碼居然都在一塊,個人不是很推薦。
我們修改原來的代碼,並做出breaking changes.
<code>package pkg
import "fmt"
// Hello says hello.
func Hello() error {
\tfmt.Println("Hello go mod!")
\treturn nil
}
// Bye says bye.
func Bye() error {
\tfmt.Println("Bye go mod!")
\treturn nil
}/<code>
並將import path 改為 v2
<code>module github.com/YouEclipse/how-to-release-go-module/v2
go 1.13/<code>
然後提交代碼,並創建tag
<code>git tag v2.0.0
git push --tags
git push master/<code>
在又等待了一段時間後(pkg.go.dev更新確實是有點慢),可以看到v2版本已經發布了,
這時候我們嘗試在之前的測試項目拉取更新
<code>go get -u github.com/YouEclipse/how-to-release-go-module/<code>
可以看到go.mod中顯示我們使用的依然是v1.0.0,顯然,v1.0.0版本的用戶並沒有受到breaking changes的影響。
如果我們要使用v2.0.0版本,修改go get的路徑為github.com/YouEclipse/how-to-release-go-module/v2即可。
至此,我們的breaking changes版本的發佈也完成了。
添加 go dev badge
大部分的開源的項目我們都可以在README中看到各種小圖標,標識著項目的各種狀態,一般稱之為badge。在pkg.go.dev之前,大部分的go項目都會添加godoc.org的badge引導開發者們去godoc.org查看文檔,但是既然使用了pkg.go.dev,我們自然就應該添加go.dev 的badge。更多的badge和相關設置可以在 https://shields.io/ 查看。
添加badge的方法和markdow添加圖片的方法一樣,只要替換項目在pkg.go.dev的路徑即可
<code>[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/url/of/your-module)/<code>
比如github.com/YouEclipse/how-to-release-go-module/v2 這個項目就可以設置成
<code>[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/YouEclipse/how-to-release-go-module?tab=doc)/<code>
效果如下
go.dev reference
這樣,我們的go module 模塊看起來就很完美了。
結語
本文從一個簡單的例子基本覆蓋了發佈go module 模塊到pkg.go.dev可能會遇到的場景,希望能給閱讀此文章的開發者提供幫助。
最後強烈推薦由年輕的大神 盛傲飛開發的 https://goproxy.cn 來作為國內 go module 的代理,速度一流,針對 Go1.13+ 如下配置即可享受它帶來的快感,沒有下載不了的包了。
go env -w GOPROXY=https://goproxy.cn,direct
附錄
1 Go modules Wiki
2 Go夜讀第 61 期 Go Modules、Go Module Proxy 和 goproxy.cn
3 go.dev: serve status badge similar to godoc.org
4 示例項目 how-to-release-go-module
5 Go module機制下升級major版本號的實踐
作者:YouEclipse,授權發佈 原文鏈接:https://juejin.im/post/5e4ccabf6fb9a07ca24f49d4
閱讀更多 Go語言中文網 的文章