Golang Tools as Dependencies
Golang is an amazing language for many reasons. One of the success developers find working with Go is the time taken to onboard a new developer to the codebase as compared to most other programming languages. An experience Go developer can quickly be productive with the new codebase when switching projects. One major contributing factors is the inbuilt Golang tools provided by the language.
However not everything in life is perfect. Even with Go development team focusing more on tools, there are still many useful tools created by the community that are the bread and butter of many projects. For example you may want to use some third party tools to help with code generation, or maybe a code linting tool. Such third party tools get updated very frequently and every release could potentially consist of breaking changes. As we add more developers to team, every workspace could consist of a different version of the tool which might produce the classic problem of “But it works on my machine”.
Go Modules dependency is the solution. One may argue that with the advancement of tooling like Dockers, this should no longer be an issue. With the recent restrictions with Docker Desktop, it may be a better idea to look at solutions closer to the language. Let’s take a look at how we achieve it using Go Modules.
Let’s say you want to use the code generation tool mockery to easily generate mocks for your Golang interaces. In your project directory with go module enabled, create a file tools.go
. Any filename works but I prefer to name and place it in the tools directory tools/tools.go
. This file will be use to record the tools dependencies that your projects needs but never compiled with your code.
//go:build tools
package tools
import (
_ "github.com/vektra/mockery/v2"
)
The //go:build tools
is added so that this file is never compiled when you are building the main
package.
Depending on the version of your Go, instead you should use the // +build tools
directive. Version 1.16 through 1.18 are in the transition period of replacing it with the //go:build
directive, hence your code might not compile if it is using the incorrect directive. For more information, visit Bug-resistant build constraints proposal
We create a simple main.go
file that consist of the interface that we are going to mock.
package main
import "fmt"
type IceCream interface {
Price() int
}
func main() {
fmt.Println("Hello Ice Cream.")
}
Next we set the GOBIN
environment variable to define where where the tools dependencies will be installed. I prefer to install them local to the project directory which is <project>/bin
. This way it allows me to quickly switch between projects using the same tool of different versions.
export GOBIN=$PWD/bin
Let’s install the tool now. We want to pin mockery to v2.13.1
. (as of writing, mockery latest version is v2.14.0
)
go get github.com/vektra/mockery/[email protected]
go install github.com/vektra/mockery/v2
Verify that the tool is installed and available.
which mockery
Now we can quickly run the tool using
mockery --name=IceCream
26 Sep 22 21:27 +08 INF Starting mockery dry-run=false version=v2.13.1
26 Sep 22 21:27 +08 INF Walking dry-run=false version=v2.13.1
26 Sep 22 21:27 +08 INF Generating mock dry-run=false interface=IceCream qualified-name=github.com/ycprog/go-projects/tools-dep version=v2.13.1
To easily use the same tool within each projects, instead of exporting GOBIN
everytime, you can instead use a relative path to the installed binary
./bin/mockery --name=IceCream
Tidy up the go.mod
file
go mod tidy
Do remember to omit the bin/
folder from git using .gitignore
/bin
How about we make it even easier for other members of the team and put them together in a Makefile
.
tools:
@export GOBIN=$(PWD)/bin
go install github.com/vektra/mockery/v2
Run the Make target
make -B tools
With the tool dependencies installed, you can add the go:generate
directive in the previous main.go
package main
import "fmt"
//go:generate ./bin/mockery --name IceCream
type IceCream interface {
Price() int
}
func main() {
fmt.Println("Hello Ice Cream.")
}
Make it lazy in your Makefile
tools:
@export GOBIN=$(PWD)/bin
go install github.com/vektra/mockery/v2
generate: tools
go generate ./...
And anyone can just run the Make
target for code generation
make -B generate
go install github.com/vektra/mockery/v2
go generate ./...
26 Sep 22 21:36 +08 INF Starting mockery dry-run=false version=v2.13.1
26 Sep 22 21:36 +08 INF Walking dry-run=false version=v2.13.1
26 Sep 22 21:36 +08 INF Generating mock dry-run=false interface=IceCream qualified-name=github.com/ycprog/go-projects/tools-dep version=v2.13.1
The full sample code can be downloaded from this Github link: https://github.com/ycprog/go-projects/tree/main/tools-dep
You May Also Like
When a Golang nil error is not nil
Coding in Golang are usually fun and easy for developers that are new to the language. There are sometimes gotcha which will catch out even experience …
Read ArticleCrash Course on Golang Benchmarks: A Beginner's Perspective
Golang, renowned for its simplicity and efficiency, employs benchmark testing as a fundamental tool for performance evaluation. In this exploration, …
Read Article