17

I'm using Go 1.11 modules for multiple repositories at this point. Right now I'm dealing with one that was already at a 1.x version. In combination with moving to go modules, I made some other breaking changes, so it's clear it's time to increment the major version of the repository. This means going to version 2.

Per "go command" documentation:

To preserve import compatibility, the go command requires that modules with major version v2 or later use a module path with that major version as the final element. For example, version v2.0.0 of example.com/m must instead use module path example.com/m/v2, and packages in that module would use that path as their import path prefix, as in example.com/m/v2/sub/pkg. Including the major version number in the module path and import paths in this way is called "semantic import versioning".

Source

Is this as simple as updating the first line of my go.mod file, appending /v2 to the module name? Or should I be creating a v2/ directory in my repository and moving all files into there?

2
  • it is the two versions will stay in production or one of them must shutdown ? Commented Nov 16, 2018 at 20:50
  • For applications and services that may be the case, but here I am working on a library repository, where some dependent services will need the new version, and other dependent services will need the old version. However I don't think that makes a difference in the answer given 'semantic import versioning'. Commented Nov 16, 2018 at 21:24

2 Answers 2

13

There are two options for how to move to version 2: branches and subdirectories. You can read more about them (with better illustrations) at https://research.swtch.com/vgo-module.

These two options are what make it possible for one version to depend on another. For example, when you implement version 2, you can update version 1 to depend on version 2 (but keep the same v1 API). Then, you only need to have one implementation of the logic for your library. This may or may not work well for you depending on the type of project, the support you want to provide, and the fixes it requires.

Branches

main: A -> B (v1.0.0) -> D (v1.0.1)
                         \
v2:                       -> C (v2.0.0)

In this scenario:

  1. You start your project on the main branch,
  2. Make some commits (A and B),
  3. Tag v1.0.0.
  4. You decide to make a breaking change. So, you create a new branch (git checkout -b v2) and make your breaking changes. Your go.mod must now be updated so the module name ends with /v2 (it's essentially a new module!).
  5. You decide to fix a bug in v1, so you go back to that branch, make a new commit, and tag a new v1 version.

When a user requires a particular version of your module, go will look in the two branches for which one provides the right module.

Subdirectories

What if you don't want to develop on branches? You can create a subdirectory for each major version. Version 1 stays at the top level, then new versions move into subdirectories:

go.mod (module example.com/foo)
foo.go
v2/
  go.mod (module example.com/foo/v2)
  foo.go

When you tag this repo with new versions, v1 will use the top level version. v2 tags will use the v2 subdirectory.

Sign up to request clarification or add additional context in comments.

2 Comments

Note that the major-branch model does not strictly require a separate branch. (You do need a separate branch if you are going to continue maintenance of the previous version — which is generally much better for your users — but if you are going to deprecate the older version and leave it unsupported, you can release v2 as a continuation of the same branch as v1.)
When doing the branch approach, one of the best exponents of this I've found is the redis golang library, they keep main as the branch with the latest version, and keep long lived branches for the past 1-2 major versions for security and vulnerability fixes.
7

From the Releasing Modules (v2 or Higher) section of the modules wiki:

There are two alternative mechanisms to release a v2 or higher module. Note that with both techniques, the new module release becomes available to consumers when the module author pushes the new tags. Using the example of creating a v3.0.0 release, the two options are:

  1. Major branch: Update the go.mod file to include a /v3 at the end of the module path in the module directive (e.g., module github.com/my/module/v3). Update import statements within the module to also use /v3 (e.g., import "github.com/my/module/v3/mypkg"). Tag the release with v3.0.0.
  • Go versions 1.9.7+, 1.10.3+, and 1.11+ are able to properly consume and build a v2+ module created using this approach without requiring updates to consumer code that has not yet opted in to modules (as described in the the "Semantic Import Versioning" section above).
  • Note: creating a new branch is not required. If instead you have been previously releasing on master, for example, and would prefer to tag v3.0.0 on master, that is a viable option as well. [...]

[...]

  1. Major subdirectory: Create a new v3 subdirectory (e.g., my/module/v3) and place a new go.mod file in that subdirectory. The module path must end with /v3. Copy or move the code into the v3 subdirectory. Update import statements within the module to also use /v3 (e.g., import "github.com/my/module/v3/mypkg"). Tag the release with v3.0.0.

However, there are additional details in that section that are worth reviewing.

One point covered there that is worth mentioned here is that if you are interested in an automated approach (for example, perhaps you have many files you would need to visit), a good automated solution is https://github.com/marwan-at-work/mod, which can automatically add, remove, or change the required /vN in your *.go code and your go.mod. See this answer for more details.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.