发布 Go Modules

每当完成一个 Go Modules 之后,如果想让更多的人使用你的代码,就需要把这个模块发布出去,这篇文章说明了如何发布一个新的模块。

原文地址:https://blog.golang.org/publishing-go-modules


简介

这个系列的文章总共有五篇,这是第三篇:

这篇文章讨论如何编写和发布模块,让其他模块可以使用它们。

请注意:这篇文章只覆盖到 v1 版本的开发。如果你对 v2 版本感兴趣,请看 Go Modules: v2 及后续版本

这篇文章中使用 git 版本管理为例子。Mercurial、Bazaar 等其他的版本管理工具也是支持的。

创建项目

在这篇文章中,你需要用一个已经存在的项目作为例子。在这里,继续使用第一篇文章的代码来演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
$ 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 文件的目录,创建一个仓库:

1
2
3
4
$ 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 进行向后不兼容的更改时,才增加主版本。只有在必要的时候才这样做。
  • 当对API进行向后兼容更改时,如更改依赖项或添加新函数、方法、结构字段或类型,则增加次版本。
  • 在做了不影响模块的公共 API 或依赖项(如修复bug)的小更改后增加补丁版本。

你可以通过附加连字符和点分隔的标识符来指定预发布版本(例如,v1.0.1-alpha或v2.2.2-beta.2)。go 命令优先使用普通版本而不是预发布版本,所以如果你的模块已经存在普通版本,用户如果要使用预发布版本,必须明确指定版本号(例如,去获取example.com/hello@v1.0.1-alpha)。

v0 主版本和预发布版本不保证向后兼容。它们允许你对 API 进行优化,不需要向用户做出稳定版本承诺。然而,v1 主版本和其他版本需要在该主版本内向后兼容。

在 go.mod 中引用的版本可能是在存储库中标记的显式发布(例如,v1.5.2),也可能是基于特定提交的 pseudo-version(例如,v0.0.0-20170915032832-14c0d48ead0c)。pseudo-version 是一种特殊类型的预发布版本。当用户需要依赖一个没有发布任何语义版本标记的项目,或者针对一个还没有打标记的提交进行开发时,伪版本是有用的。但用户不应该假定 pseudo-version 提供了一个稳定的或经过良好测试的 API。用显式的版本号标记模块,向用户表明特定的版本已经经过了充分的测试,可以使用了。

一旦你开始用版本号标记你的仓库,在后续模块的开发中,就应该继续标记新版本。当用户请求模块的新版本(使用 go get -u 或 go get example.com/hello)时,go命令将选择可用的最大语义版本,即使该版本已经存在好几年了,并且主分支后面有很多变化。继续标记新版本将使你的用户能够获得持续的改进。

不要从你的仓库中删除版本标签。如果你发现了一个版本的 bug 或安全问题,那么就发布一个新版本。如果人们依赖于你已经删除的版本,那么他们的构建可能会失败。类似地,一旦你发布了一个版本,不要更改或覆盖它。模块镜像和校验和数据库存储模块的版本和签名加密散列,以确保给定版本的构建在一段时间内是不变的。

v0:初始,不稳定版本

让我们用 v0 语义版本标记这个模块。v0 版本不做任何稳定性保证,所以几乎所有的项目都应该从v0 开始,以完善公共 API。

标记一个新版本有如下步骤:

  1. 运行 go mod tidy,删除可能不再使用的依赖项
  2. 运行 go test ./..,最后再保证所有的功能都是正常的
  3. 使用 git tag 命令标记一个新的版本
  4. 推送标记到远程仓库

1
2
3
4
5
6
7
8
$ 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/hello 的 v0.1.0 版本。对于你自己的模块,可以运行 go list -m example.com/hello@v0.1.0 来确认最新可用版本(该示例模块不存在,因此没有可用的版本)。如果你没有立即看到最新版本,并且正在使用 Go 模块代理(自 Go 1.13 以来的默认设置),可以在几分钟内再次尝试,给代理一点时间来加载新版本。

如果向公共 API 添加内容,对 v0 版本的模块进行破坏性更改,或者升级某个依赖项的次版本,则为下一个发行版增加次要版本。例如,v0.1.0 之后的下一个版本将是 v0.2.0。

如果修复了现有版本中的bug,则增加补丁版本。例如,v0.1.0之后的下一个版本将是v0.1.1。

v1:第一个稳定版本

一旦你确定你的模块 API 已经稳定了,你就可以发布 v1.0.0 了。v1 主版本告诉用户,不会对模块的API 进行不兼容的更改。他们可以升级到新的 v1 次要版本和补丁版本,他们的代码应该不会出错。函数和方法签名不会改变,导出的类型不会被删除等等。如果 API 发生了更改,它们将向后兼容(例如,向结构添加新字段),并将包含在新的次版本中。如果修复了错误(例如,安全修复),它们将包含在补丁版本中(或者作为次版本的一部分)。

有时,保持向后兼容性会导致 API 不优雅。但没关系,一个不完美的 API 总比破坏用户现有的代码要好。

标准库的 strings 包就是一个以 API 一致性为代价来维护向后兼容性的典型例子。

  • Split 函数将字符串分割成由分隔符分隔的子字符串,并返回一个由这些分隔符分割的子字符串 slice
  • SplitN 函数可以用来控制要返回的子字符串的数量

但是,Replace 函数从一开始就要确定需要替换的字符串数量(与 Split 函数不同)。

因为有 Split 函数和 SplitN 函数,你会期望也有 Replace 函数和 ReplaceN 函数。我们无法在不做破坏性修改的情况下修改 Replace 函数。在 Go 1.12中,我们添加了一个新函数,ReplaceAll。因为 Split 和 Replace 的行为不同,结果产生的 API 有点奇怪,但这种不一致性比破坏性更改要好。

假设你现在对 example.com/hello 的 API 很满意,并且希望发布 v1 作为第一个稳定版本。

给 v1 加标签的过程和给 v0 版本加标签的过程是一样的:运行go mod tidy 和 go test ./.. ,标记版本,并将标记推送到远程仓库:

1
2
3
4
5
6
7
8
$ 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 是稳定的,他们应该会用的很舒服。

结论

本文介绍了用语义版本标记模块的过程以及何时发布 v1 版本。下面的一篇文章将涵盖如何维护和发布 v2 及后续版本的模块。

请向我们发送bug 报告体验报告,帮助我们改善 Go 的依赖管理功能。

感谢你所有的反馈和帮助改进模块的建议。


另外,腾讯云区块链方向在大量招人,包括前端、后端、架构师、产品等诸多岗位,如果感兴趣,请把简历投过来 rayjun0412@gmail.com

译 / Rayjun

© 2020 Rayjun    PowerBy Hexo    京ICP备16051220号-1