diff options
-rw-r--r-- | content/migrating-to-go-modules.article | 1 | ||||
-rw-r--r-- | content/publishing-go-modules.article | 6 | ||||
-rw-r--r-- | content/using-go-modules.article | 2 | ||||
-rw-r--r-- | content/v2-go-modules.article | 182 |
4 files changed, 187 insertions, 4 deletions
diff --git a/content/migrating-to-go-modules.article b/content/migrating-to-go-modules.article index 6889fdc..975e706 100644 --- a/content/migrating-to-go-modules.article +++ b/content/migrating-to-go-modules.article @@ -11,6 +11,7 @@ This post is part 2 in a series. - Part 1 — [[/using-go-modules][Using Go Modules]] - *Part*2*—*Migrating*To*Go*Modules* (this post) - Part 3 — [[/publishing-go-modules][Publishing Go Modules]] +- Part 4 — [[/v2-go-modules][Go Modules: v2 and Beyond]] Go projects use a wide variety of dependency management strategies. [[https://golang.org/cmd/go/#hdr-Vendor_Directories][Vendoring]] tools such as [[https://github.com/golang/dep][dep]] and [[https://github.com/Masterminds/glide][glide]] are popular, but they have wide differences in behavior and don't always work well together. Some projects store their entire GOPATH directory in a single Git repository. Others simply rely on `go`get` and expect fairly recent versions of dependencies to be installed in GOPATH. diff --git a/content/publishing-go-modules.article b/content/publishing-go-modules.article index c9a37e5..8edc5e3 100644 --- a/content/publishing-go-modules.article +++ b/content/publishing-go-modules.article @@ -11,13 +11,13 @@ This post is part 3 in a series. - Part 1 — [[/using-go-modules][Using Go Modules]] - Part 2 — [[/migrating-to-go-modules][Migrating To Go Modules]] - *Part*3*—*Publishing*Go*Modules* (this post) +- Part 4 — [[/v2-go-modules][Go Modules: v2 and Beyond]] 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`. A future -article will cover developing a module at `v2` and beyond, which requires -changing the module's path. +Please note: this post covers development up to and including `v1`. If you are +interested in `v2`, please see [[/v2-go-modules][Go Modules: v2 and Beyond]]. This post uses [[https://git-scm.com/][Git]] in examples. [[https://www.mercurial-scm.org/][Mercurial]], diff --git a/content/using-go-modules.article b/content/using-go-modules.article index c5380ee..ea13545 100644 --- a/content/using-go-modules.article +++ b/content/using-go-modules.article @@ -13,6 +13,7 @@ This post is part 1 in a series. - *Part*1*—*Using*Go*Modules* (this post) - Part 2 — [[/migrating-to-go-modules][Migrating To Go Modules]] - Part 3 — [[/publishing-go-modules][Publishing Go Modules]] +- Part 4 — [[/v2-go-modules][Go Modules: v2 and Beyond]] Go 1.11 and 1.12 include preliminary [[https://golang.org/doc/go1.11#modules][support for modules]], @@ -22,7 +23,6 @@ that makes dependency version information explicit and easier to manage. This blog post is an introduction to the basic operations needed to get started using modules. -A followup post will cover releasing modules for others to use. A module is a collection of [[https://golang.org/ref/spec#Packages][Go packages]] diff --git a/content/v2-go-modules.article b/content/v2-go-modules.article new file mode 100644 index 0000000..f66f134 --- /dev/null +++ b/content/v2-go-modules.article @@ -0,0 +1,182 @@ +Go Modules: v2 and Beyond +7 Nov 2019 +Tags: tools, versioning + +Jean de Klerk + +Tyler Bui-Palsulich + +* Introduction + +This post is part 4 in a series. + +- Part 1 — [[/using-go-modules][Using Go Modules]] +- Part 2 — [[/migrating-to-go-modules][Migrating To Go Modules]] +- Part 3 — [[/publishing-go-modules][Publishing Go Modules]] +- *Part*3*—*Go*Modules:*v2*and*Beyond* (this post) + +As a successful project matures and new requirements are added, past features +and design decisions might stop making sense. Developers may want to integrate +lessons they've learned by removing deprecated functions, renaming types, or +splitting complicated packages into manageable pieces. These kinds of changes +require effort by downstream users to migrate their code to the new API, so they +should not be made without careful consideration that the benefits outweigh the +costs. + +For projects that are still experimental — at major version `v0` — occasional +breaking changes are expected by users. For projects which are declared stable +— at major version `v1` or higher — breaking changes must be done in a new major +version. This post explores major version semantics, how to create and publish a new +major version, and how to maintain multiple major versions of a module. + +* Major versions and module paths + +Modules formalized an important principle in Go, the +[[https://research.swtch.com/vgo-import][*import*compatibility*rule*]]: + + If an old package and a new package have the same import path, + the new package must be backwards compatible with the old package. + +By definition, a new major version of a package is not backwards compatible with +the previous version. This means a new major version of a module must have a +different module path than the previous version. Starting with `v2`, the major +version must appear at the end of the module path (declared in the `module` +statement in the `go.mod` file). For example, when the authors of the module +`github.com/googleapis/gax-go` developed `v2`, they used the new module path +`github.com/googleapis/gax-go/v2`. Users who wanted to use `v2` had to change +their package imports and module requirements to `github.com/googleapis/gax-go/v2`. + +The need for major version suffixes is one of the ways Go modules differs from +most other dependency management systems. Suffixes are needed to solve +the [[https://research.swtch.com/vgo-import#dependency_story][diamond dependency problem]]. +Before Go modules, [[http://gopkg.in][gopkg.in]] allowed package maintainers to +follow what we now refer to as the import compatibility rule. With gopkg.in, if +you depend on a package that imports `gopkg.in/yaml.v1` and another package that +imports `gopkg.in/yaml.v2`, there is no conflict because the two `yaml` packages +have different import paths — they use a version suffix, as with Go modules. +Since gopkg.in shares the same version suffix methodology as Go modules, the Go +command accepts the `.v2` in `gopkg.in/yaml.v2` as a valid major version suffix. +This is a special case for compatibility with gopkg.in: modules hosted at other +domains need a slash suffix like `/v2`. + +* Major version strategies + +The recommended strategy is to develop `v2+` modules in a directory named after +the major version suffix. + + github.com/googleapis/gax-go @ master branch + /go.mod → module github.com/googleapis/gax-go + /v2/go.mod → module github.com/googleapis/gax-go/v2 + +This approach is compatible with tools that aren't aware of modules: file paths +within the repository match the paths expected by `go`get` in `GOPATH` mode. +This strategy also allows all major versions to be developed together in +different directories. + +Other strategies may keep major versions on separate branches. However, if +`v2+` source code is on the repository's default branch (usually `master`), +tools that are not version-aware — including the `go` command in `GOPATH` mode +— may not distinguish between major versions. + +The examples in this post will follow the major version subdirectory strategy, +since it provides the most compatibility. We recommend that module authors +follow this strategy as long as they have users developing in `GOPATH` mode. + +* Publishing v2 and beyond + +This post uses `github.com/googleapis/gax-go` as an example: + + $ pwd + /tmp/gax-go + $ ls + CODE_OF_CONDUCT.md call_option.go internal + CONTRIBUTING.md gax.go invoke.go + LICENSE go.mod tools.go + README.md go.sum RELEASING.md + header.go + $ cat go.mod + module github.com/googleapis/gax-go + + go 1.9 + + require ( + github.com/golang/protobuf v1.3.1 + golang.org/x/exp v0.0.0-20190221220918-438050ddec5e + golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 + golang.org/x/tools v0.0.0-20190114222345-bf090417da8b + google.golang.org/grpc v1.19.0 + honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 + ) + $ + +To start development on `v2` of `github.com/googleapis/gax-go`, we'll create a +new `v2/` directory and copy our package into it. + + $ mkdir v2 + $ cp *.go v2/ + building file list ... done + call_option.go + gax.go + header.go + invoke.go + tools.go + + sent 10588 bytes received 130 bytes 21436.00 bytes/sec + total size is 10208 speedup is 0.95 + $ + +Now, let's create a v2 `go.mod` file by copying the current `go.mod` file and +adding a `v2/` suffix to the module path: + + $ cp go.mod v2/go.mod + $ go mod edit -module github.com/googleapis/gax-go/v2 v2/go.mod + $ + +Note that the `v2` version is treated as a separate module from the `v0`/`v1` +versions: both may coexist in the same build. So, if your `v2+` module has +multiple packages, you should update them to use the new `/v2` import path: +otherwise, your `v2+` module will depend on your `v0`/`v1` module. For example, +to update all `github.com/my/project` references to `github.com/my/project/v2`, +you can use `find` and `sed`: + + $ find . -type f \ + -name '*.go' \ + -exec sed -i -e 's,github.com/my/project,github.com/my/project/v2,g' {} \; + $ + +Now we have a `v2` module, but we want to experiment and make changes before +publishing a release. Until we release `v2.0.0` (or any version without a +pre-release suffix), we can develop and make breaking changes as we decide on +the new API. If we want users to be able to experiment with the new API before +we officially make it stable, we can publish a `v2` pre-release version: + + $ git tag v2.0.0-alpha1 + $ git push origin v2.0.0-alpha1 + $ + +Once we are happy with our `v2` API and are sure we don't need any other breaking +changes, we can tag `v2.0.0`: + + $ git tag v2.0.0 + $ git push origin v2.0.0 + $ + +At that point, there are now two major versions to maintain. Backwards +compatible changes and bug fixes will lead to new minor and patch releases +(for example, `v1.1.0`, `v2.0.1`, etc.). + +* Conclusion + +Major version changes result in development and maintenance overhead and +require investment from downstream users to migrate. The larger the project, +the larger these overheads tend to be. A major version change should only come +after identifying a compelling reason. Once a compelling reason has been +identified for a breaking change, we recommend developing multiple major +versions in the master branch because it is compatible with a wider variety of +existing tools. + +Breaking changes to a `v1+` module should always happen in a new, `vN+1` module. +When a new module is released, it means additional work for the maintainers and +for the users who need to migrate to the new package. Maintainers should +therefore validate their APIs before making a stable release, and consider +carefully whether breaking changes are really necessary beyond `v1`. |