paulgorman.org/technical

Go Modules and Package Management

(June, August 2018)

Go 1.11 introduces experimental support for modules, Go’s new dependency management system. Go 1.12 is expected to finalize the feature.

Go has cycled through a number of potential package management solutions. Modules come from the experimental vgo, and will soon be the official tool.

How do Go modules help us?

How does it work?

https://github.com/golang/go/wiki/Modules

A module is a collection of related Go packages that are versioned together as a single unit. Most often, a single version-control repository corresponds exactly to a single module, but alternatively a single version-control repository can hold multiple modules.

Creating a Module

$GOPATH will eventually fade away, and modules take the first step in that direction. By default, module support currently is disabled inside $GOPATH, so we create our module outside it.

$  mkdir ~/mymodule
$  cd ~/mymodule

Create a file:

package mymodule

import "fmt"

// Hello to the world!
func Hello(s string) string {
   return fmt.Sprintf("Hello, %s", s)
}

Turn our little package into a module:

$  go mod init example.com/me/mymodule
go: creating new go.mod: module example.com/me/mymodule

Go has created the new file go.mod, which contains:

module module example.com/me/mymodule

Versioned Modules

Before Go modules, anyone who included our code and did go get example.com/me/mything would get the latest version from the master branch. The consumer of the code was left at the mercy of whatever changes landed in master. Various workarounds, like vendoring, ameliorated the problem, but Go modules properly solve it.

With Go modules we can specify a particular version of code to import. Instead of fetching the latest master, by default, without specification of a particular version, Go will fetch the latest tagged version.

As an author, we should tag release versions of our module. See git-tag(1).

$  git tag v1.2.0
$  git push --tags

This tags the current commit as v1.2.0.

Go wants versions to be numbered like v2.0.13, i.e. — vMajor.Minor.Patch.

Because a major version change may (by definition) break backwards-compatibility, Go treats different major version of the same code as different modules.

Using A Module

We don’t do go get example.com/her/thing. Instead, enable modules for our project like:

$  cd myproject
$  go mod init mod

This creates a go.mod file that contains a list of required modules, and a go.sum file that includes numbered versions and checksums of modules.

Remember to add go.mod and go.sum to version control.

Go only updates a module when we explicitly ask it:

How do we jump to a different major module version? Specify it on the import line:

package main

import (
	"fmt"
	"example.com/her/thing/v2"

Running go build then automatically fetches version 2.

Note that because Go considers different major version as different modules, it’s possible to import multiple versions:

package main

import (
	"fmt"
	"example.com/her/thing/v2"
	oldThing "example.com/her/thing/"

Pull all of our dependencies into our go.mod file:

$  go get -u ./...

Vendor those dependencies:

$  go mod vendor

Odds and Ends

Modules hang around in go.mod even after we no longer import them or change major version. Clean up the module list with:

$  go mod tidy

It’s nice to tidy before tagging a release.

Vendoring. Go modules to obviate many arguments for vendoring, but vendoring is supported by the Go module tool.

$  go mod vendor
$  go build -mod vendor

Get help:

$  go help modules | less