Managing developer tools with Go module

April 2, 2024
Managing developer tools with Go module

When working on Go projects it’s not uncanny to rely on various tools during the development, like linters or generators. In a collaborative projects, where those tools are run on multiple developer machines as well as in CI running different version of such tools can result in problems that are hard to diagnose. That’s why it’s important to have a way of managing the versioning to keep it consistent. In this post, I’ll show you one that find particularly useful.

Tools as dependencies

Start with creating a new module in tools directory, as well as a tools.go file. For each tool you want to use as a project dependency and include it in go mod, you need to add an import, proceeded with „_”.

💡 Another approach Creating a separate module is not the only approach. Similar thing can be achieved by using build tags in the tools package instead. However, all tools will land in the main go.mod .

From now on, you can use go run to spin up the tools, which will be module aware, hence use the version from the go.mod file.

    
  
    
  

Installing tools locally

Even though Go does a good job caching the modules, each time you run the tool it needs to be built. There are also possibilities to run into problems when being offline. To avoid those problems binaries can be built once and kept local to the project.

It can be automated using any build tool, for example Makefile. Let’s start with getting the list of tools to be installed:

    
  

We also need some helper targets which will ensure that everything’s in place and up-to-date for installing the tools.

    
  

And finally the tools installation itself:

    
  

To make sure that our Makefile will work for people who have go workspace configured, it’s also good to add the following check:

    
  

Otherwise, running it in a workspace will result in go: -modfile cannot be used in workspace mode

Remember how tools were invoked with go run? It required passing the whole import path. It’s quite cumbersome and can be avoided using some ”make magic“ to allow using tool’s name as a Make variable.

    
  

I really like this approach as it separates the code dependencies from the tool ones and guarantees that the same version of the tools used on each machine that runs Make targets. Do have favorite ways of managing tools versioning for Go projects? Let me know!

Go