I have recently studied go mod and have compiled it into an article. This article is a systematic look at go mod, not a simple introduction to how to use it.

Before go mod came out, the community used a model similar to NodeJS, vendor, which meant that all packages were stored in the vendor directory, but this approach was obviously not elegant enough, and then came go mod, which has become the standard for module management in the Go community until today.

Concepts

  • module. a folder with go.mod is a module. in short, a module that contains several packages.
  • package. A number of *.go files in a folder make up a package, and at the top of each .go file, there is a package xxx to declare what package it is.

Versions

module has a version, Go adheres to Semantic Versioning 2.0.0, so versions will basically look like this: v0.0.1, v1.2.3, v2.0.0 and so on. Some repositories don’t have a tag, so Go will automatically generate a version number, called pseudo-version, which is a pseudo-version number, e.g. v0.0.0-20191109021931-daa7c04131f5. The approximate format is.

  • vX.0.0 base version
  • Timestamp in the format yyyymmddhhmmss, the time value is the UTC time of that commit
  • The first 12 characters of the commit hash

The three above, linked together with -.

Big versions

Maybe we have a program that comes up 2.0, 3.0, 4.0, so what is the solution that Go offers? The answer is to create a subdirectory, e.g. for v2 versions, create a v2 subdirectory, for v3 create a v3 subdirectory. Or, if it’s at the top level, add a v2 or v3 suffix to the end of the path declared in go.mod.

To be honest, it’s not very elegant. His decision was largely based on the guideline that

If an old package and a new package have the same import path, the new package must be backwards compatible with the old package.

This means that code within the same major version number must be compatible.

One of the problems with Go is that, for example, when upgrading from v2 to v3, there may be some incompatibilities, but most of the code is still compatible and not completely rewritten. When you do this, the caller was importing github.com/x/aaa, but now you have to import github.com/x/aaa/v2, and all the references to it have to be changed. In addition, the aaa.XXXStruct in v2 and the aaa.XXXStruct in v1 are incompatible and cannot be assigned to each other, which causes the caller to change a lot of things and creates a lot of work.

GOPROXY, GOPRIVATE and other common environment variables

  • In areas where the network is restricted (e.g. China) go get is basically not directly usable. So you can use the GOPROXY environment variable to set up a proxy server.

    1
    
    $ go env -w GOPROXY="https://goproxy.cn,direct"
    
  • If your project is private then you will also need to add the corresponding path to the GOPRIVATE variable.

    1
    
    $ go env -w GOPRIVATE="github.com/your_name,git.example.com"
    

    If there is only one, write one. If there are more than one, concatenate them with commas. For private repositories, it is recommended to also set a GONOSUMDB with the same value as it.

go.mod file

As we said above, where there is a go.mod file, there is a module, so let’s look at the format of go.mod, starting with an example.

1
2
3
4
5
6
7
8
9
module example.com/my/thing

go 1.12

require example.com/other/thing v1.0.2
require example.com/new/thing/v2 v2.3.4
exclude example.com/old/thing v1.2.3
replace example.com/bad/thing v1.4.5 => example.com/good/thing v1.4.5
retract [v1.9.0, v1.9.5]
  • module declares the path to the module itself
  • go Declare the version of Go used by this module
  • require Declare the modules that this module depends on
  • exclude tells the go mod not to load this module
  • replace Declare the replacement, => before the path in the code and after the path to replace it with
  • retract Declare the version of this module that is broken, or the version to be withdrawn so that people don’t use it. The effect is that the user will be prompted when they encounter the version they have set

All the paths in there are in the format of a URL with a space and a version number, e.g. example.com/new/thing/v2 v2.3.4.

Version selection

How does go mod do version selection? In a nutshell: start with the current project main, build a dependency tree, and when multiple submodules depend on the same module, choose the latest one.

go mod Version selection

As shown above, main depends on A1.2 , B1.2 , A1.2 depends on C1.3 , while B1.2 depends on C1.4 and they also depend on D1.2 . Finally, go mod will select main , A1.2 , B1.2 , C1.4 , D1.2 .

Subdirectories

There are times when we want to give a version to a subdirectory, so how do we do that? The answer is to just prefix the version number with the path to the directory, e.g.

1
2
3
module/
        - A
        - A/B

Then we run git tag A/B/v0.1.2 and that’s it. This way the user will give preference to the version of the subdirectory over the version number of the root directory when using the package.

Common commands

Let’s look at common commands that have something to do with version control.

  • go mod init github.com/xxx/yyy Declare a module
  • go mod tidy adds packages that the project depends on to the go.mod file, and removes packages in the go.mod file that the project doesn’t need.
  • go mod why Explain the dependency chain of an imported dependency
  • go build build the binary
  • go get add dependencies
  • go get -u update dependencies