on
Introduction to Go Modules
Table of Contents
Preface
All the content for this post was gathered by reading the 7 part blog series by Russ Cox and other notes. There is also an introductory post on the Go blog. I would encourange you to read them as well.
Why Go Modules
Before jumping into the workings of Go modules, we should first understand why we need it in the first place. The current state of Go development has some drawbacks:
- All code needs to be in the $GOPATH/src directory. Such a structure is not compatible with many developer workflows. Why can’t I work in a different directory ?
- It depends on the existence of tools such as git, bzr, fossil etc on the host because
go get
essentially just clones the repo based on where it is stored. No guarantee that builds of the same codebase will be identical.
go get
will look first locally. If not present it will fetch the latest.go get -u
will always fetch the latest. Both these options depend on the current state of $GOPATH.
Philosophy
In order to understand Go modules, you have to understand a few key concepts.
Import Compatibility Rule
If an old package and a new package have the same import path, the new package must be backwards-compatible with the old package.
Minimal Version Selection
Defaults to using the oldest allowed version of every package involved in the build. These exclusions and replacements do not apply when the module is being built as a dependency of some other module. This gives users full control over how their own programs build, but not over how other people’s programs build.
Builds should be Reproducible / Verifiable / Verified
Reproducible is one that, when repeated, produces the same result. Verifiable is one that records enough information to be precise about exactly how to repeat it. Verified is one that checks that it is using the expected source code.
Example
Let’s explain with an example. We will first use go modules to create two versions of a library and push that module to github. Then we will create an executable, that uses this newly created library.
Module Creation
The library we create will print out messages in different colors. All code for this is available on github. Here are the steps to create this library:
Create directory structure and populate
main.go
. I created the following directory structure outside my $GOPATH:gomodexample cmd gomodexample main.go
The contents of main.go is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
package gomodexample import "github.com/fatih/color" // PrintInRed prints the given message in red func PrintInRed(msg string) { color.Red(msg) } // PrintInGreen prints the given message in green func PrintInGreen(msg string) { color.Green(msg) } // PrintInYellow prints the given message in yellow func PrintInYellow(msg string) { color.Yellow(msg) } // PrintInBlue prints the given message in blue func PrintInBlue(msg string) { color.Blue(msg) }
In the root of the directory structure initialize the module
go mod init github.com/jimmyislive/gomodexample
This creates two files in the root:
go.mod
andgo.sum
. (These files are always checked into source control)If you
cat go.mod
, you won’t see anything interesting:module github.com/jimmyislive/gomodexample
Do a
go build ./...
in the root dir.go.mod
now contains much more info i.e using info in this file, builds are now Reproducible:jjohn$ go list -m all github.com/jimmyislive/gomodexample github.com/fatih/color v1.7.0 github.com/mattn/go-colorable v0.1.1 github.com/mattn/go-isatty v0.0.7 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223
As you can see, the
go.mod
file now contains the exact dependencies and versions which are required for this program to work.The
go.sum
contains cryptographic checksums for the different versions of dependencies currently being used. i.e. builds are verifiedTag the code and push to github
git tag v1.0.0 git push origin v1.0.0
We will now add a new function and tag that version of the library v1.1.0:
1 2 3 4 5
// PrintInCyan prints the given message in cyan func PrintInCyan(msg string) { color.Cyan(msg) }
Thus, both v1.0.0 and v1.1.0 are now available on github.
We have now packaged our library gomodexample
as a module and it can be used by anybody.
Module Use
Let’s try using the library we just created. Following similar steps as above:
Create the executable
mkdir -p ~/gomoduse/cmd/gomoduse touch gomoduse/cmd/gomoduse/main.go
Contents would be like:
1 2 3 4 5 6 7 8 9 10 11 12
package main import "github.com/jimmyislive/gomodexample/cmd/gomodexample" func main() { gomodexample.PrintInRed("I hope i'm in red") gomodexample.PrintInGreen("I hope i'm in green") gomodexample.PrintInBlue("I hope i'm in blue") gomodexample.PrintInYellow("I hope i'm in yellow") gomodexample.PrintInCyan("I hope i'm in cyan") }
Again we are using modules. In this executable we want to use the latest i.e. v1.1.0 version of our library
Here is the output:go mod init github.com/jimmyislive/gomoduse go build ./... go run cmd/gomoduse/main.go jjohn$ go run cmd/gomoduse/main.go
If you peek in go.mod you will see that the version of gomodexample is pinned to the latest i.e. v1.1.0
jjohn$ go list -m all github.com/jimmyislive/gomoduse github.com/fatih/color v1.7.0 github.com/jimmyislive/gomodexample v1.1.0 github.com/mattn/go-colorable v0.1.1 github.com/mattn/go-isatty v0.0.7 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223
Now let’s experiment and downgrade the version of gomodexample to v1.0.0. First we confirm to see what versions are out there in the wild:
jjohn$ go list -m -versions github.com/jimmyislive/gomodexample github.com/jimmyislive/gomodexample v1.0.0 v1.1.0
Downgrade the gomodexample library:
jjohn$ go get github.com/jimmyislive/gomodexample@v1.0.0 go: finding github.com/jimmyislive/gomodexample v1.1.0 go: downloading github.com/jimmyislive/gomodexample v1.0.0 go: downloading github.com/jimmyislive/gomodexample v1.1.0
Verifying that we now have v1.0.0:
jjohn$ go list -m all github.com/jimmyislive/gomoduse github.com/fatih/color v1.7.0 github.com/jimmyislive/gomodexample v1.0.0 github.com/mattn/go-colorable v0.1.1 github.com/mattn/go-isatty v0.0.7 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223
Now when we build and run the executable, we should get an error as the old v1.0.0 version does not contain the
PrintInCyan
function.jjohn$ go build ./... # github.com/jimmyislive/gomoduse/cmd/gomoduse cmd/gomoduse/main.go5: undefined: gomodexample.PrintInCyan
Thus the
go.mod
controls which version of dependencies are used thereby giving us reliable and verifiable builds.
Hopefully this tutorial has helped you understand Go modules better. Starting in Go 1.13 (August 2019) module mode will be enabled by default. The new features seem very promising and I for one am eager to start using Go modules more often.