Go Modules and Package Management

(June, August 2018, March 2019)

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?

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.

A module is a collection of Go packages stored in a file tree with a go.mod file at its root. The go.mod file defines the module’s module path, which is also the import path used for the root directory, and its dependency requirements, which are the other modules needed for a successful build. Each dependency requirement is written as a module path and a specific semantic version.

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
go: creating new go.mod: module

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

module module

Versioned Modules

Before Go modules, anyone who included our code and did go get 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 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 (

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 (
	oldThing ""

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

$  go get -u ./...

Vendor those dependencies:

$  go mod vendor

Odds and Ends

List the modules used by the project:

$  go list -m all

Modules hang around in go.mod even after we no longer import them or change major version. Clean unused modules from 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

If something gets borked, like a cached package was incompletely downloaded:

$  go clean -modcache