Package management in Go
Brief overview of package management in Go — pre and post Go modules
- By Jai
- ·
- Insights
- Go
Package management is one of the things Go has always missed. One of the major drawbacks of the previous (pre 1.11) go get
was lack of support for managing dependency versions and enabling reproducible builds. The community has developed package managers and tools like Glide, dep and many others serving as de-facto solutions for versioning dependencies.
"I use go get for production builds." — said no one ever.
Go's implementation of package management traces its origins back to Google (which has a giant monolithic repository for all their source code). Let's break down on what's wrong with 'pre - go module' package management tooling.
- Versioning dependencies
- Vendoring dependencies
- The necessity of
GOPATH
Versioning dependencies
go get
by default didn't support module versioning. The idea behind the first version of go's package management was — no need for module versioning, no need for 3rd-party module repositories, you build everything from your current branch.
Pre Go 1.11, adding a dependency meant cloning that dependency's source code repo in your GOPATH
. That was about it. There was no concept of versions. Rather, it always pointed to the current master branch at the time of cloning. Another major issue cropped up when different projects needed different versions of a dependency — which wasn't possible either.
Vendoring dependencies
Package vendoring is commonly referred to as the case where dependent packages are stored in the same place as your project. That usually means your dependencies are checked into your source management system, such as Git.
Consider this case — A uses dependency B, which uses a feature of dependency C introduced in version 1.5 of C, B must be able to ensure that A's build uses C 1.5 or later. Pre Go 1.5, there was no mechanism for carrying dependency code alongside commands without rewriting import paths.
Necessity of GOPATH
GOPATH
exists for two main reasons:
- In Go, the
import
declaration references a package via its fully qualified import path.GOPATH
exist so that from any directory insideGOPATH/src
the go tool can compute the absolute import path of the package in question. - A location to store dependencies fetched by
go get.
What's wrong with this?
GOPATH
doesn’t allow checking out the source of a project in a directory of choice like they are used to with other languages.- Additionally,
GOPATH
does not let the developer have more than one copy of a project (or its dependencies) checked out at the same time.
Introducing Go Modules
Go 1.11 introduces preliminary support for Go modules. From Go Wiki,
A module is a collection of related Go packages that are versioned together as a single unit. Modules record precise dependency requirements and create reproducible builds.
Go modules brings three important features built-in,
go.mod
file similar topackage.json
orPipfile
.- A machine-generated transitive dependency description -
go.sum
. - No more
GOPATH
limitation. Modules can be in any path.
$ go help mod
Go mod provides access to operations on modules.
Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.
Usage:
go mod <command> [arguments]
The commands are:
download download modules to local cache
edit edit go.mod from tools or scripts
graph print module requirement graph
init initialize new module in current directory
tidy add missing and remove unused modules
vendor make vendored copy of dependencies
verify verify dependencies have expected content
why explain why packages or modules are needed
Use "go help mod <command>" for more information about a command.
Relevant discussion thread.
Migrating to Go Modules
To use Go modules, update Go to version >= 1.11
. Since GOPATH
is going away, one can activate module support in one of these two ways:
- Invoke the
go
command in a directory outside of theGOPATH/src
tree, with a validgo.mod
file in the current directory. - Go modules don't work if source is under
GOPATH
. To override this behaviour, invoke thego
command withGO111MODULE=on
environment variable set.
Let's start porting by following these simple steps:
- As
GOPATH
isn't necessary anymore, move the module out ofGOPATH
. - From the project root, create the initial module definition -
go mod init github.com/username/repository
. The best part is,go mod
automatically converts dependencies from existing package managers likedep
,Gopkg
,glide
and six others. This will create a file calledgo.mod
with the module name and dependencies with its versions.
$ cat go.mod
module github.com/deepsourcelabs/cli
go 1.12
require (
github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e
github.com/getsentry/raven-go v0.2.0
github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9
- Run
go build
to create ago.sum
file which contains the expected cryptographic checksums of the content of specific module versions. This is to ensure that future downloads of these modules retrieve the same bits as the first download. Note that go.sum is not a lock file.
$ cat go.sum
github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e h1:9574pc8MX6rF/QyO14SPHhM5KKIOo9fkb/1ifuYMTKU=
github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9 h1:dIsTcVF0w9viTLHXUEkDI7cXITMe+M/MRRM2MwisVow=
github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Note on versioning: To maintain backward compatibility, if the module is version v2 or higher, the major version of the module must be included as a
/vN
at the end of the module paths used ingo.mod
files (e.g.,module github.com/username/repository/v2
Everyday commands
List dependencies
go list -m all
lists the current module and all its dependencies.
$ go list -m all
github.com/deepsourcelabs/cli
github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e
github.com/getsentry/raven-go v0.2.0
github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9
In the
go list
output, the current module, also known as the main module, is always the first line, followed by dependencies sorted by module path.
List available versions of a package
go list -m -versions github.com/username/repository
lists available versions of a package.
$ go list -m -versions github.com/getsentry/raven-go
github.com/getsentry/raven-go v0.1.0 v0.1.1 v0.1.2 v0.2.0
Add a dependency
Adding a dependency is implicit. After importing a dependency in code, running go build
or go test
command gets the latest version of the module and adds it to go.mod
file. If you would like to add a dependency explicitly, rungo get github.com/username/repository
.
Upgrade/downgrade a dependency
go get github.com/username/[email protected]
downloads and sets the specific version of the dependency and updates go.mod
file.
$ go get github.com/getsentry/[email protected]
go: finding github.com/getsentry/raven-go v0.1.2
go: downloading github.com/getsentry/raven-go v0.1.2
go: extracting github.com/getsentry/raven-go v0.1.2
$ cat go.mod
module github.com/deepsourcelabs/marvin-go
go 1.12
require (
github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e
github.com/getsentry/raven-go v0.1.2
github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9
)
$ cat go.sum
github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e h1:9574pc8MX6rF/QyO14SPHhM5KKIOo9fkb/1ifuYMTKU=
github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/getsentry/raven-go v0.1.2 h1:4V0z512S5mZXiBvmW2RbuZBSIY1sEdMNsPjpx2zwtSE=
github.com/getsentry/raven-go v0.1.2/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9 h1:dIsTcVF0w9viTLHXUEkDI7cXITMe+M/MRRM2MwisVow=
github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Vendoring dependencies
When using modules, the go command completely ignores vendor directories. For backward compatibility with older versions of Go, or to ensure that all files used for a build are stored together in a single file tree, rungo mod vendor
.
This creates a directory named vendor
in the root directory of the main module and stores all the packages from dependency modules there.
Note: To build using the main module's top-level vendor directory, run 'go build -mod=vendor'.
Remove unused dependencies
go mod tidy
trims unused dependencies and updates go.mod
file.
FAQs
Is GOPATH
not needed anymore?
No. Farewell GOPATH
.
Which version is pulled by default?
The go.mod file and the go command more generally use semantic versions as the standard form for describing module versions, so that versions can be compared to determine which should be considered earlier or later than another. A module version like v1.2.3
is introduced by tagging a revision in the underlying source repository. Untagged revisions can be referred to using a "pseudo-version" like v0.0.0-yyyymmddhhmmss-abcdefabcdef
, where the time is the commit time in UTC and the final suffix is the prefix of the commit hash.
Should go.sum
be checked into version control?
Yes.