Golang Tools as Dependencies

  • YC YC
  • |
  • 26 September 2022
post-thumb

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

comments powered by Disqus

You May Also Like