The Go Blog
Publishing Go Modules
Tyler Bui-Palsulich
26 September 2019
Introduction
This post is part 3 in a series.
- Part 1 —Using Go Modules
- Part 2 —Migrating To Go Modules
- Part 3 — Publishing Go Modules (this post)
- Part 4 —Go Modules: v2 and Beyond
- Part 5 —Keeping Your Modules Compatible
Note: For documentation on developing modules, seeDeveloping and publishing modules.
This post discusses how to write and publish modules so other modules can dependon them.
Please note: this post covers development up to and includingv1
. If you areinterested inv2
, please seeGo Modules: v2 and Beyond.
This post usesGit in examples.Mercurial,Bazaar, and others are supported as well.
Project setup
For this post, you’ll need an existing project to use as an example. So, startwith the files from the end of theUsing Go Modules article:
$ cat go.modmodule example.com/hellogo 1.12require rsc.io/quote/v3 v3.1.0$ cat go.sumgolang.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.gopackage helloimport "rsc.io/quote/v3"func Hello() string { return quote.HelloV3()}func Proverb() string { return quote.Concurrency()}$ cat hello_test.gopackage helloimport ( "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 newgit
repository and add an initial commit. If you’republishing your own project, be sure to include aLICENSE
file. Change to thedirectory containing thego.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"$
Semantic versions and modules
Every required module in ago.mod
has asemantic version, the minimum version of that dependencyto use to build the module.
A semantic version has the formvMAJOR.MINOR.PATCH
.
- Increment the
MAJOR
version when you make abackwards incompatiblechange to the public API of your module.This should only be done when absolutely necessary. - Increment the
MINOR
version when you make a backwards compatible change to the API,like changing dependencies or adding a new function,method, struct field, or type. - Increment the
PATCH
version after making minor changes that don’t affectyour module’s public API or dependencies, like fixing a bug.
You can specify pre-release versions by appending a hyphen and dot separatedidentifiers (for example,v1.0.1-alpha
orv2.2.2-beta.2
). Normal releasesare preferred by thego
command over pre-release versions, so users must askfor 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 backwardscompatibility. They let you refine your API before making stability commitmentsto your users. However,v1
major versions and beyond require backwardscompatibility within that major version.
The version referenced in ago.mod
may be an explicit release tagged in therepository (for example,v1.5.2
), or it may be apseudo-version based on aspecific commit (for example,v0.0.0-20170915032832-14c0d48ead0c
).Pseudo-versions are a special type of pre-release version. Pseudo-versions areuseful when a user needs to depend on a project that has not published anysemantic 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-testedAPI. Tagging your modules with explicit versions signals to your users thatspecific versions are fully tested and ready to use.
Once you start tagging your repo with versions, it’s important to keep taggingnew releases as you develop your module. When users request a new version ofyour module (withgo get -u
orgo get example.com/hello
), thego
commandwill choose the greatest semantic release version available, even if thatversion is several years old and many changes behind the primary branch.Continuing to tag new releases will make your ongoing improvements available toyour users.
Do not delete version tags from your repo. If you find a bug or a security issuewith a version, release a new version. If people depend on a version that youhave deleted, their builds may fail. Similarly, once you release a version, donot change or overwrite it. Themodule mirror and checksum databasestore modules, their versions, and signed cryptographic hashes to ensure thatthe build of a given version remains reproducible over time.
v0: the initial, unstable version
Let’s tag the module with av0
semantic version. Av0
version does not makeany stability guarantees, so nearly all projects should start withv0
as theyrefine 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 onv0.1.0
ofexample.com/hello
. For your ownmodule, you can rungo list -m example.com/hello@v0.1.0
to confirm the latestversion is available (this example module does not exist, so no versions areavailable). If you don’t see the latest version immediately and you’re using theGo module proxy (the default since Go 1.13), try again in a few minutes to givethe proxy time to load the new version.
If you add to the public API, make a breaking change to av0
module, orupgrade the minor or version of one of your dependencies, increment theMINOR
version for your next release. For example, the next release afterv0.1.0
would bev0.2.0
.
If you fix a bug in an existing version, increment thePATCH
version. Forexample, the next release afterv0.1.0
would bev0.1.1
.
v1: the first stable version
Once you are absolutely sure your module’s API is stable, you can releasev1.0.0
. Av1
major version communicates to users that no incompatiblechanges will be made to the module’s API. They can upgrade to newv1
minor andpatch releases, and their code should not break. Function and method signatureswill not change, exported types will not be removed, and so on. If there arechanges to the API, they will be backwards compatible (for example, adding a newfield to a struct) and will be included in a new minor release. If there are bugfixes (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’sOK. An imperfect API is better than breaking users’ existing code.
The standard library’sstrings
package is a prime example of maintainingbackwards compatibility at the cost of API consistency.
Split
slices a string into allsubstrings separated by a separator and returns a slice of the substringsbetween those separators.SplitN
can be used to control the number of substrings to return.
However,Replace
took a count of howmany instances of the string to replace from the beginning (unlikeSplit
).
GivenSplit
andSplitN
, you would expect functions likeReplace
andReplaceN
. But, we couldn’t change the existingReplace
without breakingcallers, which we promised not to do. So, in Go 1.12, we added a new function,ReplaceAll
. The resulting API is alittle odd, sinceSplit
andReplace
behave differently, but thatinconsistency is better than a breaking change.
Let’s say you’re happy with the API ofexample.com/hello
and you want toreleasev1
as the first stable version.
Taggingv1
uses the same process as tagging av0
version: rungo mod tidy
andgo 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, thev1
API ofexample.com/hello
is solidified. Thiscommunicates to everyone that our API is stable and they should feel comfortableusing it.
Conclusion
This post walked through the process of tagging a module with semantic versionsand when to releasev1
. A future post will cover how to maintain and publishmodules atv2
and beyond.
To provide feedback and help shape the future of dependency management in Go,please send usbug reports orexperience reports.
Thanks for all your feedback and help improving Go modules.
Next article:Working with Errors in Go 1.13
Previous article:Go 1.13 is released
Blog Index
[8]ページ先頭