01
介绍
本文讨论如何编写和发布模块,以便其他模块可以依赖它们。
请注意:这篇文章涵盖了开发版本和 v1 版本。
这篇文章在示例中使用了 Git。但是也支持 Mercurial,Bazaar 和其他组织代码工具。
如果您对 Git 不够了解,强烈推荐先阅读以下文章:
- Git安装和初始设置
- Git学习之基本操作(一)
- Git学习之分支的操作(二)
- Git学习之回溯历史版本(三)
- Git学习之消除冲突和修改提交信息(四)
- Git学习之压缩历史(五)
- Git学习之推送至远程仓库(六)
- Git学习之从远程仓库获取(七)
02
项目设置
对于本文,您需要一个现有项目作为示例。所以,我们可以使用「Go Modules 介绍与基本操作」中创建的项目。
$ 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 存储库并添加初始提交。如果要发布自己的项目,请确保包括许可证文件。进入包含 go.mod 的目录,然后创建存储库:
$ git init $ git add LICENSE go.mod go.sum hello.go hello_test.go $ git commit -m "hello: initial commit" $
03
语义版本和模块
go.mod 中每个必需的模块都有一个语义版本,即用于构建模块的依赖项的最小版本。
语义版本具有 vmajor. MINOR. patch 形式。
- 当您对模块的公共 API 进行向后不兼容的更改时,请增加主要版本。并且只有在绝对必要的时候才这么做。
- 当您对 API 进行向后兼容更改(如更改依赖关系或添加新函数、方法、结构字段或类型)时,请增加 MINOR 版本。
- 在进行不影响模块公共 API 或依赖项(如修复 Bug)的小更改后,增加 PATCH 版本。
您可以通过附加连字符和点分隔标识符(例如 v1.0.1-alpha 或 v2.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 -u 或 go get example.com/hello)时,go 命令将选择可用的最大语义版本,即使该版本已使用多年,并且主分支后面有许多更改。继续标记新版本将使您的持续改进可供用户使用。
不要从存储库中删除版本标记。如果发现版本有错误或安全问题,请发布新版本。否则,如果用户依赖于您已删除的版本,则其生成可能会失败。同样,发布版本后,不要更改或覆盖它。模块镜像和校验数据库存储模块、其版本和已签名的加密哈希,以确保给定版本的生成在一段时间中保持可重复性。
04
v0:初始的不稳定版本
让我们使用 v0 语义版本标记模块。v0 版本不提供任何稳定性保证,因此几乎所有项目在优化其公共 API 时都应以 v0 开始。
标记新版本有几个步骤:
- 运行 go mod tidy,它删除模块可能累积的任何不再需要的依赖项。
- 最后运行 go test ./...,以确保一切工作。
- 使用 git 标记使用新版本标记项目。
- 将新标记推送到源存储库。
$ 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 $
现在,其他项目可以依赖于 v0.1.0 example.com/hello。对于您自己的模块,您可以运行 go list -m example.com/hello@v0.1.0 以确认最新版本可用(此示例模块不存在,因此没有可用的版本)。如果您没有立即看到最新版本,并且正在使用 Go 模块代理(自 Go 1.13 以来的默认值),请在几分钟内重试,为代理提供加载新版本的时间。
如果添加到公共 API,对 v0 模块进行大改,或升级其中一个依赖项的次要或版本,请增加下一个版本的次要版本。例如,v0.1.0 之后的下一个版本将是 v0.2.0。
如果在现有版本中修复了 Bug,请增加 PATCH 版本。例如,v0.1.0 之后的下一个版本将是 v0.1.1。
05
v1:第一个稳定版本
一旦您绝对确定模块的 API 是稳定的,就可以发布 v1.0.0。v1 主要版本向用户传达不会对模块的 API 进行不兼容的更改。他们可以升级到新的 v1 次要版本和修补程序版本,并且其代码不应中断。函数和方法签名不会更改,导出的类型不会删除,等等。如果 API 有更改,则它们向后兼容(例如,向结构添加新字段),并将包含在新的次要版本中。如果有错误修复(例如,安全修补程序),它们将包含在修补程序版本中(或作为次要发布的一部分)。
有时,保持向后兼容性可能会导致 API 尴尬。没关系。不完美的 API 比破坏用户的现有代码更好。
标准库的字符串包是以 API 一致性为成本保持向后兼容性的主要示例。
- Split 将字符串拆分为由分隔符分隔的所有子字符串,并返回这些分隔符之间的子字符串切片。
- SplitN 拆分可用于控制要返回的子字符串数。
但是,"Replace"从一开始就计算了要替换的字符串的实例数(与拆分不同)。
Split 和 SplitN,您将期望使用 Replace 和 ReplaceN 等功能。但是,如果不中断调用,我们无法更改现有的"替换",我们承诺不会这样做。因此,在 Go 1.12 中,我们添加了一个新函数"ReplaceAll"。生成的 API 有点奇怪,因为Split 和 Replace 的行为不同,但这种不一致比一个突破性的变化更好。
假设您对一个文档的 API 很满意 example.com/hello 并且希望将 v1 发布为第一个稳定版本。
标记 v1 使用与标记 v0 版本相同的过程:运行 go mod tidy 和 go 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/hello 的 v1 API 封版。这向每个人传达我们的 API 是稳定的,他们应该放心地使用它。
06
总结
本文介绍如何通过标记具有语义版本的模块以及何时发布 v1 的过程。