The aim of this workshop is to familiarise you with the basics of Go modules. By the end, you should know how to:
- build a project that uses modules
- create a project that uses modules
- keep module dependencies up to date
- manage module dependencies, exclusions and replacements
- deal with dependencies that don't use Go modules
- have understanding of some external Go module tools.
You should have a computer with Go 1.11, git installed on it, and a github account.
Note that all the information below can be found in the go
command help pages.
Specifically, see:
go help modules
go help go.mod
go help mod
go help list
go help module-get
First, find a project that supports modules; then use the Go command to build it. Note that you should not be inside your $GOPATH directory.
For example:
git clone https://github.com/myitcv/gobin
cd gobin
go build
./gobin --help
Make a directory for the module:
mkdir /tmp/testmodule
cd /tmp/testmodule
Create the module:
go mod init github.com/myname/testproject
Write a Go file that imports something:
package main
import (
"fmt"
"log"
"gopkg.in/yaml.v2"
)
func main() {
data, err := yaml.Marshal(map[string]string{
"some yaml": "values\netc",
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", data)
}
Build the program:
go build
Run the binary:
./testproject
Now look at the go.mod
file. It has been automatically changed to reflect the dependencies in use.
cat go.mod
You can list dependencies with go list
.
To list all the dependencies that the project has (this recursively includes testing dependencies too):
go list all
There is also a module variant of the go list
command. To list all modules in the current project:
go list -m all
The go mod
subcommand provides some useful tools for exploring why module dependencies are there.
For example, you might be wondering why some particular package was listed by the go list all
command above. The go mod why
command is your friend. It prints the shorted dependency path leading to a given package.
go mod why text/template
If you want to know why some particular module (as opposed to package) is used:
go mod why -m gopkg.in/check.v1
The go mod graph
command shows the entire dependency graph, including modules that aren't actually in use but were used to arrive at the current dependency graph.
If we wish to update a dependency version, we can change the go.mod
file directly. But it's generally easier to use the go get
command to do that.
For example, to use an earlier version of the gopkg.in/yaml.v2
repository, we could run:
go get gopkg.in/[email protected]
Try this, and see how this changes the go.mod
file.
Semantic versions (described at https://semver.org) are the cornerstone of the Go module system. It's useful to understand how semantic versions work before diving into modules.
What's a semantic version? In the simplest case:
vMAJOR.MINOR.PATCH
where MAJOR, MINOR and PATCH are numbers. For example:
v2.3.4
Modules are expected to retain compatibility within the same major version (with some exceptions).
In the general form, a semantic version can also include a pre-release version and a patch version:
vMAJOR.MINOR.PATCH-PRE+PATCH
For example:
v2.3.4-alpha+amd64
Semantic versions have a well defined ordering. MAJOR, MINOR and PATCH are compared in precedence order. If PRE is present, the version compares earlier. PATCH is ignored when comparing. PRE may contain multiple parts separated by dots; earlier parts take precedence over later parts, and parts consisting entirely of digits are compared numerically; numeric parts always compare before non-numeric parts.
Rearrange the following semantic versions in order:
v1.2.1-foo.5
v1.2.1-foo.12
v1.0.0
v1.1.0
v2.0.1
v1.2.1-foo.beta
v1.2.1-foo
v1.2.1-foo.0alpha
v1.2.1-foo-beta
v1.2.2
v1.2.1
v1.2.0
v1.2.1-foo.alpha
Sometimes we'll want to use a dependency that hasn't yet been tagged with a version. The Go tool uses pseudo-versions to represent dependencies in this case.
Some examples:
- a module might not have any tagged versions at all
- you want to use the tip version of a package
- you want to use some other branch of a package
A pseudo-version is just another semantic version except that it uses the PRE part of the version (the prerelease part) to represent the commit. A pseudo-version comes in one of three flavours. The first is used when there are no commits earlier than the required version. It holds the date of the commit followed by its commit hash:
v1.2.3-20181128-59aa596
The second is used when there is already a tagged version before the required version (note that
v1.2.4-0.20181128-59aa596
If the most recent commit before the required version already has a PRE section, the pseudo-version will look like this:
v1.2.3-PRE.0.20181128-59aa596
Although it's useful to know how these work, we almost never have to write out a pseudo-version by hand, because the go tool does it for us.
Try this out with the github.com/rogpeppe/module-workshop
repository. Add a call to workshop.Version
(it just returns a string identifying the version) to your code, and the relevant import statement.
Then experiment with using go get github.com/rogpeppe/module-workshop@$version
for different versions (there are tags for all the versions in the above Semantic Versions second).
When there's a major version change (except major versions 0 and 1), the import path for a module changes.
This means it's possible to have two major versions of a module loaded at the same time.
There is already a v2 version of github.com/rogpeppe/module-workshop
available. Change your import to import from github.com/rogpeppe/module-workshop/v2
and try to compile the code. Then fix the code.
Experiment with importing both the v1 version and the v2 version.
It's common to need to experiment on external code; this was easy when there was a global mutable GOPATH directory, but less so when all modules are read-only. The gohack command tries to make this straightforward again.
First use your new go modules-fu to build the gohack command. It's in github.com/rogpeppe/gohack
.
You can use gohack to copy a temporary copy of any module to your gohack
directory ($HOME/gohack by default). For example:
gohack get github.com/rogpeppe/module-workshop
Run this command and take a look at your go.mod
file. Observe that it's added a replace
clause.
Experiment with editing the checked out code and recompiling your program.
You can drop the replace statement with
gohack undo github.com/rogpeppe/module-workshop
or just
gohack undo
to undo all replace statements that refer to local directories.
Although the go command adds new dependencies automatically, it doesn't remove them.
It's always good practice to run the go mod tidy
command to do that before committing.
go mod tidy
Try that and see what difference it makes to your go.mod
file.