按https://golang.org/doc/copyright.html, 原文内容使用 Creative Commons Attribution 3.0 License, 代码使用 BSD license.
使用原文之外的部分, 需注明出处: https://blog.csdn.net/big_cheng/article/details/107436139.
Publishing Go Modules
Tyler Bui-Palsulich
26 September 2019
This post is part 3 in a series.
This post discusses how to write and publish modules so other modules can depend on them.
Please note: this post covers development up to and including v1. If you are interested in v2, please see Go Modules: v2 and Beyond.
This post uses Git in examples. Mercurial, Bazaar, and others are supported as well.
For this post, you’ll need an existing project to use as an example. So, start with the files from the end of the Using Go Modules article:
$ 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)
}
}
$
Next, create a new git repository and add an initial commit. If you’re publishing your own project, be sure to include a LICENSE file. Change to the directory containing the go.mod then create the repo:
$ git init
$ git add LICENSE go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: initial commit"
$
Every required module in a go.mod has a semantic version, the minimum version of that dependency to use to build the module.
A semantic version has the form vMAJOR.MINOR.PATCH.
You can specify pre-release versions by appending a hyphen and dot separated identifiers (for example, v1.0.1-alpha or v2.2.2-beta.2). Normal releases are preferred by the go command over pre-release versions, so users must ask for pre-release versions explicitly (for example, go get example.com/hello@v1.0.1-alpha) if your module has any normal releases.
v0 major versions and pre-release versions do not guarantee backwards compatibility. They let you refine your API before making stability commitments to your users. However, v1 major versions and beyond require backwards compatibility within that major version.
The version referenced in a go.mod may be an explicit release tagged in the repository (for example, v1.5.2), or it may be a pseudo-version based on a specific commit (for example, v0.0.0-20170915032832-14c0d48ead0c). Pseudo-versions are a special type of pre-release version. Pseudo-versions are useful when a user needs to depend on a project that has not published any semantic version tags, or develop against a commit that hasn’t been tagged yet, but users should not assume that pseudo-versions provide a stable or well-tested API. Tagging your modules with explicit versions signals to your users that specific versions are fully tested and ready to use.
Once you start tagging your repo with versions, it’s important to keep tagging new releases as you develop your module. When users request a new version of your module (with go get -u or go get example.com/hello), the go command will choose the greatest semantic release version available, even if that version is several years old and many changes behind the primary branch. Continuing to tag new releases will make your ongoing improvements available to your users.
Do not delete version tags from your repo. If you find a bug or a security issue with a version, release a new version. If people depend on a version that you have deleted, their builds may fail. Similarly, once you release a version, do not change or overwrite it. The module mirror and checksum database store modules, their versions, and signed cryptographic hashes to ensure that the build of a given version remains reproducible over time.
Let’s tag the module with a v0 semantic version. A v0 version does not make any stability guarantees, so nearly all projects should start with v0 as they refine their public API.
Tagging a new version has a few steps:
Run go mod tidy, which removes any dependencies the module might have accumulated that are no longer necessary.
Run go test ./… a final time to make sure everything is working.
Tag the project with a new version using git tag.
Push the new tag to the origin repository.
$ 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 $
Now other projects can depend on v0.1.0 of example.com/hello. For your own module, you can run go list -m example.com/hello@v0.1.0 to confirm the latest version is available (this example module does not exist, so no versions are available). If you don’t see the latest version immediately and you’re using the Go module proxy (the default since Go 1.13), try again in a few minutes to give the proxy time to load the new version.
If you add to the public API, make a breaking change to a v0 module, or upgrade the minor or version of one of your dependencies, increment the MINOR version for your next release. For example, the next release after v0.1.0 would be v0.2.0.
If you fix a bug in an existing version, increment the PATCH version. For example, the next release after v0.1.0 would be v0.1.1.
Once you are absolutely sure your module’s API is stable, you can release v1.0.0. A v1 major version communicates to users that no incompatible changes will be made to the module’s API. They can upgrade to new v1 minor and patch releases, and their code should not break. Function and method signatures will not change, exported types will not be removed, and so on. If there are changes to the API, they will be backwards compatible (for example, adding a new field to a struct) and will be included in a new minor release. If there are bug fixes (for example, a security fix), they will be included in a patch release (or as part of a minor release).
Sometimes, maintaining backwards compatibility can lead to awkward APIs. That’s OK. An imperfect API is better than breaking users’ existing code.
The standard library’s strings package is a prime example of maintaining backwards compatibility at the cost of API consistency.
However, Replace took a count of how many instances of the string to replace from the beginning (unlike Split).
Given Split and SplitN, you would expect functions like Replace and ReplaceN. But, we couldn’t change the existing Replace without breaking callers, which we promised not to do. So, in Go 1.12, we added a new function, ReplaceAll. The resulting API is a little odd, since Split and Replace behave differently, but that inconsistency is better than a breaking change.
Let’s say you’re happy with the API of example.com/hello and you want to release v1 as the first stable version.
Tagging v1 uses the same process as tagging a v0 version: run go mod tidy and go test ./…, tag the version, and push the tag to the origin repository:
$ 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
$
At this point, the v1 API of example.com/hello is solidified. This communicates to everyone that our API is stable and they should feel comfortable using it.
This post walked through the process of tagging a module with semantic versions and when to release v1. A future post will cover how to maintain and publish modules at v2 and beyond.
To provide feedback and help shape the future of dependency management in Go, please send us bug reports or experience reports.
Thanks for all your feedback and help improving Go modules.
模块版本号 = major/主 + minor/小 + patch/补丁:
后向兼容修改时(如加方法/函数/类型/struct字段、改依赖) 增加小版本号;
不兼容修改时(尽量避免) 增加大版本号;
小修改不影响兼容性时 增加补丁版本号.
pre-release: 临时版本如"v1.0.1-alpha、v2.2.2-beta.2".
pseudo-version: 伪版本一般对应一次commit如"v0.0.0-20170915032832-14c0d48ead0c", 也属于pre-release.
v0版: 在1.0之前的版本, 视为不稳定, 不保证兼容性.
v1版: 首个稳定版本 - api不再会有不兼容修改、方法/函数签名不再改动、已公开类型不会被删除.
具体步骤:
go mod tidy;
测试;
包括 go.mod, go.sum, *.go, LICENSE;
提交、打tag.
举例:
strings.Split(s, sep string) 拆分字符串.
strings.SplitN(s, sep string, n int) 拆分指定次数.
strings.Replace(s, old, new string, n int) 替换指定次数.
strings.ReplaceAll(s, old, new string) (Go 1.12).
Replace已经带n参数, 如果将其改名ReplaceN 会导致所有已经依赖它的代码不能工作.
如上Split -> ReplaceAll, SplitN -> Replace 有点奇怪. 虽然不能做到对称 Split -> Replace, SplitN -> ReplaceN, 但总比破坏用户代码强.