Using the Go module

Posted by BillBillJr on Sat, 31 Aug 2019 22:07:41 +0200

brief introduction

Go will finally have its own modules. Previously, only packages were available, and modules were the parent of packages.

Below is a summary of the two articles you read on the official website.

Using the Go module

A module is a collection of Go packages stored in a file tree with a go.mod file in the root directory.

The go.mod file defines the module's _module path_, which is also the import path for the root directory.
The _dependency requirements_ is also defined, which is the external module required for building.

Common operations of modules

Create a new module

Beyond $GOPATH/src, create a directory and add the file hello.go:

package hello

func Hello() string {
    return "Hello, world."
}

Add a test file, 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)
    }
}

At this point, the directory contains a package, but it is not a module because there is no go.mod file yet.

If you run go test in this directory:

$ go test
PASS
ok      _/home/gopher/hello    0.020s
$

The last line summarizes all the package tests because we are outside the $GOPATH directory and any modules.
All go commands do not know any import path to the current directory, which creates a fake module based on the current directory name:
_/home/gopher/hello.

Now use go mod init to initialize the directory as a module and rerun go test.

$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello
$ go test
PASS
ok      example.com/hello    0.020s
$

This creates a Go module and runs the tests. Notice that the module name has changed to example.com/hello.

The go mod init command produces a go.mod file.

$ cat go.mod
module example.com/hello

go 1.12
$

The go.mod file only appears in the module's root directory.
The import path of the package in the subdirectory is the module path plus the subdirectory path.
If we create a subdirectory called world, the package will be automatically identified as
Part of module example.com/hello, whose import path is example.com/hello/world.

Add a dependency

The main motivation of the Go module is to improve the experience that code is dependent on by other developers.

Update hello.go, import rsc.io/quote and use it to implement Hello functions.

package hello

import "rsc.io/quote"

func Hello() string {
    return quote.Hello()
}

Rerun the test:

$ go test
go: finding rsc.io/quote v1.5.2
go: downloading rsc.io/quote v1.5.2
go: extracting rsc.io/quote v1.5.2
go: finding rsc.io/sampler v1.3.0
go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: downloading rsc.io/sampler v1.3.0
go: extracting rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
PASS
ok      example.com/hello    0.023s
$

The go command resolves the import using the specific dependencies listed in go.mod.
When it encounters any import ed package that is not in go.mod,
GoNaming automatically finds the module that contains this package and adds it to the go.mod file.
And use the latest version number.

$ cat go.mod
module example.com/hello

go 1.12

require rsc.io/quote v1.5.2
$

Running go test again will not repeat the above process, because go.mod is up-to-date.
The downloaded modules are cached locally in the $GOPATH/pkg/mod directory.

Adding a direct dependency usually results in some indirect dependency, the command go list -m all
List the current module and all its dependencies.

$ go list -m all
example.com/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
$

Usually the first line is the main module, followed by its dependencies, sorted by the module path.

The version of golang.org/x/text is v0.0.0-20170915032832-14c0d48ead0c.
This is a _pseudo-version_, which is the version syntax of the go command for submissions without labels.

In addition to go.mod, the go command maintains a file called go.sum.
Encrypted hash containing the contents of each specific version of the module.

$ cat go.sum
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZO...
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:Nq...
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3...
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPX...
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/Q...
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9...
$

The go command has the same bit as the first download by using the go.sum file for future downloads.
Make sure that the modules your project depends on do not change unexpectedly. These two files go.mod and go.sum
They should all be saved under version control.

Upgrade Dependency

With the Go module, the version number uses the semantic version label. A semantic version has three parts: the major version, the minor version,
Patch version.

Because you saw golang.org/x/text using an unmarked version from go list-m all earlier,
Let's upgrade to the latest version and test that everything is OK.

$ go get golang.org/x/text
go: finding golang.org/x/text v0.3.0
go: downloading golang.org/x/text v0.3.0
go: extracting golang.org/x/text v0.3.0
$ go test
PASS
ok      example.com/hello    0.013s
$

Everything is OK. Take a look at the output of go list-ml and the go.mod file again.

$ go list -m all
example.com/hello
golang.org/x/text v0.3.0
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
$ cat go.mod
module example.com/hello

go 1.12

require (
    golang.org/x/text v0.3.0 // indirect
    rsc.io/quote v1.5.2
)
$

The golang.org/x/text package has been upgraded to the latest version. The go.mod file has also been updated.
The indirect comment indicates that this is not a direct dependency used by the module, but an indirect dependency.

Let's try to update the minor version of rsc.io/sampler, do the same, and run the tests:

$ go get rsc.io/sampler
go: finding rsc.io/sampler v1.99.99
go: downloading rsc.io/sampler v1.99.99
go: extracting rsc.io/sampler v1.99.99
$ go test
--- FAIL: TestHello (0.00s)
    hello_test.go:8: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
FAIL
exit status 1
FAIL    example.com/hello    0.014s
$

The test failed and this version is incompatible with our use case. See which versions of this module are available:

$ go list -m -versions rsc.io/sampler
rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99
$

Try another version. We've already used 1.3.0. Maybe 1.3.1 is compatible.

$ go get rsc.io/sampler@v1.3.1
go: finding rsc.io/sampler v1.3.1
go: downloading rsc.io/sampler v1.3.1
go: extracting rsc.io/sampler v1.3.1
$ go test
PASS
ok      example.com/hello    0.022s
$

By specifying the version number in the go get, the default is @latest.

Add dependency on the new master version

Add a new feature to modify the hello.go file:

package hello

import (
    "rsc.io/quote"
    quoteV3 "rsc.io/quote/v3"
)

func Hello() string {
    return quote.Hello()
}

func Proverb() string {
    return quoteV3.Concurrency()
}

Then add a new test in hello_test.go:

func TestProverb(t *testing.T) {
    want := "Concurrency is not parallelism."
    if got := Proverb(); got != want {
        t.Errorf("Proverb() = %q, want %q", got, want)
    }
}

Then retest:

$ go test
go: finding rsc.io/quote/v3 v3.1.0
go: downloading rsc.io/quote/v3 v3.1.0
go: extracting rsc.io/quote/v3 v3.1.0
PASS
ok      example.com/hello    0.024s
$

Notice that our module now relies on rsc.io/quote and rsc.io/quote/v3.

$ go list -m rsc.io/q...
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
$

Each major version of a GO module uses a different path: starting with v2, the path must end with the major version number.
For example, the V3 version of rsc.io/quote is no longer rsc.io/quote, but a stand-alone name rsc.io/quote/v3.
This usage is called semantic import versioning and gives incompatible packages (packages with different major versions) a different name.

The go command allows you to build at most one version that contains any particular module path, meaning at most one path per major version:
One rsc.io/quote, one rsc.io/quote/v2, one rsc.io/quote/v3, etc.
This gives the module author a clear rule as to whether a copy of the module path is possible: a program cannot be used at the same time
rsc.io/quote v1.5.2 and rsc.io/quote v1.6.0. At the same time, different major versions of a module allow coexistence.
Consumers who can help with the module can progressively upgrade to a new major version.

In this example, we want to use quote.Concurrency from rsc/quote/v3 v3.1.0, but we haven't
Be prepared to migrate your use of rsc.io/quote v1.5.2.

In large programs or code libraries, the ability to migrate step by step is particularly important.

Upgrade depends on the new major version

Let's continue with the migration and start with rsc.io/quote/v3 only. Due to changes in the main version, we anticipate some APIs
It may have been removed, renamed, or changed in other incompatible ways.

By reading the documentation, we found that Hello has become HelloV3:

$ go doc rsc.io/quote/v3
package quote // import "rsc.io/quote/v3"

Package quote collects pithy sayings.

func Concurrency() string
func GlassV3() string
func GoV3() string
func HelloV3() string
func OptV3() string
$

We changed quote.Hello() in hello.go to quoteV3.HelloV3() by upgrading.

package hello

import quoteV3 "rsc.io/quote/v3"

func Hello() string {
    return quoteV3.HelloV3()
}

func Proverb() string {
    return quoteV3.Concurrency()
}

On this node, we don't need to rename the import, so we can do this:

package hello

import "rsc.io/quote/v3"

func Hello() string {
    return quote.HelloV3()
}

func Proverb() string {
    return quote.Concurrency()
}

Rerun the test and everything is OK.

Remove unused dependencies

We've removed the use of rsc.io/quote, but it still appears in go list-m all
And go.mod files.

Why? Because go build and go test can easily tell us that something is missing.
It needs to be added, but we can't tell that something can be safely removed.

Remove a dependency only if all packages in the module have been checked, and all possible combinations of build tags for those packages
A common build command does not load this information, so it cannot safely remove dependencies.

go mod tidy cleans up these unused packages.

conclusion

Go modules in Go are the future of management dependency. Module functionality is available in all supported Go versions (after GO 1.11).

Summarize the workflow for using the Go module:

  • go mod init creates a new module to initialize the go.mod file
  • go build, go test, and other package build commands add the necessary dependencies to the go.mod file
  • Go list-m all prints out the dependencies of the current module
  • go get changes a required version of a dependency
  • go mod tidy removes unused dependencies

Migrate to Go Module

Go Projects have a variety of dependency management strategies. Vendoring tools, such as dep or glide, are
Very popular, but they behave very differently and are not well compatible.
Some items store their entire GOPATH directory in the Git repository.
Others may rely only on go get and expect to install the latest dependencies in GOPATH.

The Go module system, introduced in Go 1.11, provides an official dependency solution based on the go command.

This tool and the technology to turn a project into a module are described below.

Note: If your project is already at version 2.0.0 or above, when you add a go.mod file,
You need to upgrade your module path.

Migrate your project to the Go module

A project that is about to be converted to the Go module may be in three situations:

  • A brand new Go project
  • A Go project using Dependency Manager
  • A Go project without any dependent managers

The latter two cases will be discussed here.

Dependency Manager Existing

Convert a project that already has a dependency manager using the following command:

$ git clone https://github.com/my/project
[...]
$ cd project
$ cat Godeps/Godeps.json
{
    "ImportPath": "github.com/my/project",
    "GoVersion": "go1.12",
    "GodepVersion": "v80",
    "Deps": [
        {
            "ImportPath": "rsc.io/binaryregexp",
            "Comment": "v0.2.0-1-g545cabd",
            "Rev": "545cabda89ca36b48b8e681a30d9d769a30b3074"
        },
        {
            "ImportPath": "rsc.io/binaryregexp/syntax",
            "Comment": "v0.2.0-1-g545cabd",
            "Rev": "545cabda89ca36b48b8e681a30d9d769a30b3074"
        }
    ]
}
$ go mod init github.com/my/project
go: creating new go.mod: module github.com/my/project
go: copying requirements from Godeps/Godeps.json
$ cat go.mod
module github.com/my/project

go 1.12

require rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$

go mod init creates a new go.mod file and automatically imports the dependencies.
From Godeps.json, Gopkg.lock, or Other supported formats Medium.

The go mod init parameter is the module path, where the module was found.

Run go build or go test before proceeding. Subsequent steps may modify your go.mod file.
If you want to iterate through updates, this is the go.mod file closest to your module dependency specification.

$ go mod tidy
go: downloading rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
go: extracting rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$ cat go.sum
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca h1:FKXXXJ6G2bFoVe7hX3kEX6Izxw5ZKRH57DFBJmHCbkU=
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
$

go mod tidy looks for packages that are actually imported in your module. It adds module dependencies to packages that are not in any known module.
Delete any modules that do not provide import packages. If a module provides packages that are only used by projects that have not yet been migrated to the module system,
This module dependency uses the // indirect comment tag.

When submitting changes to go.mod for version control, a good habit is to run go mod tidy first.

Finally, check that the code was built successfully and pass the test.

$ go build ./...
$ go test ./...
[...]
$

Note that other dependency managers may specify dependencies at the level of individual packages or warehouses as a whole.
It is also not usually possible to identify the dependencies specified in go.mod. Therefore, you may not be able to get them
The exact same version of each package as before, and the upgrade can have devastating changes.
Therefore, it is important to execute the above commands by auditing the generated dependencies.

Run the following command:

$ go list -m all
go: finding rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
github.com/my/project
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$

It is appropriate to determine which version is selected by comparing your old dependency management files.
If you find that a version is not what you want, you can go mod why -m
Or go mod graph command to find out why and use go get
Upgrade or downgrade to the correct version.

If demotion is required, go get may also demote other dependencies to meet minimum compatibility.

$ go mod why -m rsc.io/binaryregexp
[...]
$ go mod graph | grep rsc.io/binaryregexp
[...]
$ go get rsc.io/binaryregexp@v0.2.0
$

No package manager is used

For Goprojects that do not use the package manager, start by creating a go.mod file:

$ git clone https://go.googlesource.com/blog
[...]
$ cd blog
$ go mod init golang.org/x/blog
go: creating new go.mod: module golang.org/x/blog
$ cat go.mod
module golang.org/x/blog

go 1.12
$

There are no previous dependency management files to refer to, so go mod init creates
A go.mod file with only module and go instructions.
In this example, we set the module path to golang.org/x/blog because
That's it Custom Import Path.
Users may use this path to import packages, so we must be careful not to change it.

The module directive declares the module path, and the go directive declares compatibility
The Go language version of this module.

Now run go mod tidy to add dependencies:

$ go mod tidy
go: finding golang.org/x/website latest
go: finding gopkg.in/tomb.v2 latest
go: finding golang.org/x/net latest
go: finding golang.org/x/tools latest
go: downloading github.com/gorilla/context v1.1.1
go: downloading golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
go: downloading golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
go: extracting github.com/gorilla/context v1.1.1
go: extracting golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
go: downloading gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
go: extracting gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
go: extracting golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
go: downloading golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
go: extracting golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
$ cat go.mod
module golang.org/x/blog

go 1.12

require (
    github.com/gorilla/context v1.1.1
    golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
    golang.org/x/text v0.3.2
    golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
    golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
    gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
)
$ cat go.sum
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
git.apache.org/thrift.git v0.0.0-20181218151757-9b75e4fe745a/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
[...]
$

go mod tidy adds module dependencies to all packages imported directly into your module and creates a go.sum file
Save the checksum for each library at a specific version. Check to see if it passes the build and test:

$ go build ./...
$ go test ./...
ok      golang.org/x/blog    0.335s
?       golang.org/x/blog/content/appengine    [no test files]
ok      golang.org/x/blog/content/cover    0.040s
?       golang.org/x/blog/content/h2push/server    [no test files]
?       golang.org/x/blog/content/survey2016    [no test files]
?       golang.org/x/blog/content/survey2017    [no test files]
?       golang.org/x/blog/support/racy    [no test files]
$

Note that when go mod tidy adds a dependency, it adds the latest version of the module.
If your GOPATH includes an older version of dependency, the new version may bring
Destructive changes, you may be in go mod tidy, go build or go test
You see an error.

If this happens, try downgrading your version with go get or compatible with your module
Latest version of each dependency.

Testing under modules

Some tests may need to be adjusted after migrating to the Go module.

If a test needs to write files in the package directory, it may fail when the package directory is in the module cache.
Because it is read-only, this may cause go test all to fail under certain circumstances.
The test should copy the files it needs and write them to a temporary directory.

If a test relies on a relative path (.. /package-in-another-module) to locate and
Reads files in another package, and if the package is in another module, the test will fail.
This path will be located in the versioned subdirectory of the module cache, or a replace d subdirectory
Directive specifies the path.

In this case, you may need to copy the test input to your module or enter the test input
From the original file to the data embedded in the.go source file.

If the test requires the go command to run the test in GOPATH mode, it may fail.
In this case, you may need to add a go.mod file to the source code tree to be tested.
Or explicitly set GO111MODULE=off.

Publish a version

Ultimately, you should mark a version for your module and publish it. This is optional if you haven't published any versions yet.
However, there is no officially released version, and downstream users use pseudo versions on specific submissions.
This may be harder to support.

$ git tag v1.2.0
$ git push origin v1.2.0

Your new go.mod file defines a canonical import path for your module and adds the lowest dependent version.
If your users are already using the correct import path, your dependencies will not change destructively.
Adding the go.mod file is backward compatible, but it is a significant change that may expose existing problems.

If you already have an existing version, you should increase the major version number.

Import and specification module path

Each module declares its module path in go.mod.
Each import statement that references a package in a module must prefix the package path with the module path.
However, the go command may encounter a number of differences
Remote Import Path Warehouse of.
For example, both golang.org/x/link and github.com/golang/link can work
Resolves to a code repository hosted in go.googlesource.com/link.
The go.mod file contains a repository declared golang.org/x/lint, so this is the only path
Corresponds to a valid module.

Go 1.4 provides a mechanism for declaring the import path of a specification by
// import comment,
But package authors don't always provide them.
Therefore, code written prior to the module's appearance may have used an irregular module import path.
There were no errors due to path mismatches.
When using modules, the import path must match the canonical module path, so you need to update the import statement.
For example, you may need to change import "github.com/golang/lint" to
import "golang.org/x/lint".

Another problem is that for modules with a major version greater than or equal to 2, the canonical path of the module is not consistent with its repository path.
A Go module with a major version greater than 1 must be suffixed with the major version number after its module path.
For example, version 2.0.0 must add a suffix/v2. However, the import statement may reference
Package in module, but no version suffix is provided.
For example, non-module users may have used this in version v2.0.1
github.com/russross/blackfriday
Instead of
github.com/russross/blackfriday/v2,
Need to update import path to include suffix/v2.

summary

Converting to the Go module should be a simple process for most users.
Errors may accidentally result from noncanonical import paths or destructive dependencies.

Topics: Go github git JSON Google