You can carry out package dependency management in Go efficiently with the help of modules. Modules specify the requirements of our project, list all the required dependencies, and help us keep track of the specific versions of installed dependencies.
What is a module?
Go modules are a group of related packages that are versioned and distributed together. Modules are identified by a module path that is declared in the first line of the go.mod file in our project.
We can get started using modules in our project by executing the command go mod init module-name in our terminal:
$ go mod init exampleThis will generate a go.mod file in the root of our project, with the following contents:
module example
go 1.17In layperson's terms, the go.mod file stipulates all the project requirements and lists all the needed dependencies for our project.
Installing dependencies
We can install a new package dependency via the go get command: this will download the package and its dependencies into our project. The go get command can even install complete Go programs in our project! The only requirement is for the program to contain the main function.
Let's try installing the fatih/color and kyokomi/emoji/v2 packages hosted on GitHub.
$ go get github.com/fatih/color
$ go get github.com/kyokomi/emoji/v2After the program executes the commands, it will automatically add the github.com/fatih/color and github.com/kyokomi/emoji/v2 package dependencies to our go.mod file. The updated go.mod file will have the following contents:
module example
go 1.17
require (
github.com/fatih/color v1.13.0 // indirect
github.com/kyokomi/emoji/v2 v2.2.8 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
)Since we don't use the fatih/color and the kyokomi/emoji/v2 packages anywhere in our project, they are marked with an indirect comment. This comment will also appear on an indirect dependency package that in simple terms is just a dependency of another dependency.
After we're done installing our new dependencies, let's go ahead and try out the fatih/color and kyokomi/emoji/v2 packages by importing them in our main.go file:
package main
import (
"github.com/fatih/color"
"github.com/kyokomi/emoji/v2"
)
func main() {
programmer := emoji.Sprint(":man_technologist:")
emoji.Printf(":woman_technologist:")
color.Green(" Learning about modules! " + programmer)
}This will output a green-colored and emoji-delimited message in our terminal:
👩💻 Learning about modules! 👨💻 Go projects often have many external dependencies. In case we want to see all the dependencies of the current module, we can execute the command go list -m all. This will output into our terminal all the dependencies of the current module, including indirect ones:
example
github.com/fatih/color v1.13.0
github.com/kyokomi/emoji/v2 v2.2.8
github.com/mattn/go-colorable v0.1.9
github.com/mattn/go-isatty v0.0.14
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87cIn the first line, we have example, which is the name of our module. After the first line, we can see all the other dependencies that we've installed in our project.
Dependency authentication
After installing the first dependency in our project via go get command, the go tool will automatically generate a go.sum file. This file contains the SHA-256 hash of all the currently installed dependencies. In simple terms, we can use this file to verify the integrity of the downloaded packages.
In case we install a new dependency after we've created the go.sum file, the go tool will automatically add the SHA-256 hash (checksum) of the new dependency to the go.sum file. Let's go ahead and look at the contents of our go.sum file right now:
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE=
github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
...In the first line, we can see the package name github.com/fatih/color, then we can see the package version v1.13.0, and at the end, we can see the package checksum h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=.
The main purpose of go.sum is to authenticate our modules against unexpected or malicious changes that can break the code in our project. Apart from that, if we stop using a module in our project, the recorded checksum information will allow us to resume doing it in the future.
Deleting unused dependencies
During the development process, we will often end up with many unused dependencies in our project. We can delete them with the help of the go mod tidy command.
After executing go mod tidy, the program will update the go.mod file and remove the unused dependencies.
For example, let's modify our main.go file and remove the import of the fatih/color package:
package main
import (
"github.com/kyokomi/emoji/v2"
)
func main() {
programmer := emoji.Sprint(":man_technologist:")
emoji.Printf(":woman_technologist:")
emoji.Println(" Learning about modules! " + programmer)
}Now let's execute the go mod tidy command in our terminal and look at the updated contents of our go.mod file:
module example
go 1.17
require github.com/kyokomi/emoji/v2 v2.2.8We can see that the program has removed the fatih/color dependency, as well as all of its own dependencies.
As a good practice, we should always execute the go mod tidy command after making changes in our project. This will ensure that our go.mod file is accurate and clean.
Upgrading dependencies
Go modules are versioned in Semantic Versioning (SemVer) format. For example, a package in version 1.6.2 means that 1 is the major version, 6 is the minor version and 2 is the patch version.
We can automatically update a dependency to its latest version via the go get -u command. This will also update the go.mod file to reflect the new version of the dependency. The -u flag means that the latest minor or patch version will be used.
In special cases, we might need to install a previous version of a dependency. We can do this with the go get command by specifying the precise version at the end of the dependency. Let's try installing a previous version of the fatih/color package:
$ go get github.com/fatih/[email protected]If we look at the contents of our go.mod file, we will see a previous version of the fatih/color package installed:
module example
go 1.17
require (
// now we have the version v1.12.0 instead of the latest version v1.13.0!
github.com/fatih/color v1.12.0
github.com/kyokomi/emoji/v2 v2.2.8
)
require (
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
)To get the latest fatih/color package version once again, we can execute the following in our terminal:
$ go get -u github.com/fatih/colorSummary
Let's briefly remember what we've learned in this topic:
Go modules are a group of related packages that are versioned and distributed together.
We can start using modules in our project via the
go mod init module-namecommand. This will automatically create a go.mod file that will list all the needed dependencies for our project.After using
go getto install a new package into our project, the program will automatically add package dependencies to our go.mod file.After installing the first new package, the program will create a go.sum file in our project. The main purpose of this file is to keep a checksum that can authenticate our modules against unexpected changes.
Remember to always execute the
go mod tidycommand after making changes in our project. This will help us remove unused dependencies in our project, and keep our go.mod file accurate and clean.
Enough theory! Let's go ahead and do some exercises to test our knowledge of modules.