原创分享 如何优雅地发布 go module 模块

astaxie · 2020年02月20日 · 最后由 astaxie 回复于 2020年02月21日 · 2002 次阅读
本帖已被设为精华帖!

前言

截止到 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 简介

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 添加两个方法和相关注释

package pkg

import "fmt"

// Hello says hello.
func Hello() {
    fmt.Println("Hello go mod!")
}

// Bye says bye.
func Bye() {
    fmt.Println("Bye go mod!")
}

执行 go mod init 生成go.mod文件

go mod init
go: creating new go.mod: module github.com/YouEclipse/how-to-release-go-module

内容如下:

module github.com/YouEclipse/how-to-release-go-module

go 1.13

我们把代码 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 envgo env -w 命令查看和修改。

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

当我们成功拉取后,可以在 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:

git tag v1.0.0

打上标签后 push 到远端分支,等待一段时间,我们就可以在 pkg.go.dev 上看到我们发布的 v1.0.0 版本了。

(这里有些奇怪,之前的 v0.0.0 消失了,不知道什么原因)

我们执行go get -u github.com/YouEclipse/how-to-release-go-module 即可获取最新发布的版本

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

发布 breaking changes

在早期没有 go module 时,假设我们引用的第三方包的做了 breaking changes,API 发生改变,在跑 CI 或者重新拉取第三方包后,代码将会编译失败。我印象比较深刻的是在 2018 年左右,go.uuid 的 API 发生了 breaking changes,将原来没有返回 err 的函数返回了 err,而当时我们没有任何包管理,都是在 docker 镜像更新时通过 go get 拉取,这就导致当时我们的 CI 都跑失败了。

go 官方其实有关于版本控制的最佳实践,叫做Semantic Import Version,即语义化版本。 关于语义化版本的说明,附录中官方的 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.

package pkg

import "fmt"

// Hello says hello.
func Hello() error {
    fmt.Println("Hello go mod!")
    return nil
}

// Bye says bye.
func Bye() error {
    fmt.Println("Bye go mod!")
    return nil
}

并将 import path 改为 v2

module github.com/YouEclipse/how-to-release-go-module/v2

go 1.13

然后提交代码,并创建 tag

git tag v2.0.0
git push --tags
git push master

在又等待了一段时间后 (pkg.go.dev 更新确实是有点慢),可以看到 v2 版本已经发布了,

这时候我们尝试在之前的测试项目拉取更新

go get -u github.com/YouEclipse/how-to-release-go-module

可以看到 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 和相关设置可以在shields.io查看。

添加 badge 的方法和 markdow 添加图片的方法一样,只要替换项目在 pkg.go.dev 的路径即可

[![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)

比如github.com/YouEclipse/how-to-release-go-module/v2 这个项目就可以设置成

[![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) 

效果如下

go.dev reference

这样,我们的 go module 模块看起来就很完美了。

结语

本文从一个简单的例子基本覆盖了发布 go module 模块到 pkg.go.dev 可能会遇到的场景,希望能给阅读此文章的开发者提供帮助。

附录

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 版本号的实践

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
astaxie 将本帖设为了精华贴 02月20日 22:31

我是文章的作者,文中一处有误,我在掘金的原文已经做了更改

“我们可以随便建一个项目,执行 go get -u github.com/YouEclipse/how-to-release-go-module ” 这里的前提是这个项目要执行 go mod init 或者是已经有 go.mod 的项目,否则不会使用 go module,也就不会走到 proxy.golang.org 了。因为 1.13 版本下 go module 依然不是默认开启的,而是 auto.

YouEclipse 回复

哈哈,我昨天就说让你自己来发吧

mahuaibo GoCN 每日新闻 (2020-02-22) 中提及了此贴 02月22日 15:29
smallfish1 GoCN 每日新闻 (2020-02-24) 中提及了此贴 02月24日 14:45
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册