Skip to content

Instantly share code, notes, and snippets.

@antichris
Last active February 23, 2022 16:49
Show Gist options
  • Save antichris/c78afa6bcf187dd940ba9d11e722e6d3 to your computer and use it in GitHub Desktop.
Save antichris/c78afa6bcf187dd940ba9d11e722e6d3 to your computer and use it in GitHub Desktop.
Adding tools to your Go module

I think I'll just summarize my findings down here; it didn't all seem obvious and took some googling and tinkering, as some sources are a bit ambiguous on some of the points.

All native: tools.go

The best practice as endorsed by the Go team (golang/go#25922 (comment)).

//go:build tools
// +build tools

package yourpackage

import (
	_ "golang.org/x/tools/cmd/stringer"
	// ...
)

Change yourpackage to the name of the package that you put this in, e.g., use qux if you put it at the root of your module foo/bar/qux. Only use tools when you actually put it in a tools package (directory).

If you're on Go 1.17+, you don't need the // +build tools line.

go.mod

To add the dependencies and the latest versions of these tools to your go.mod and freshen the module cache, run

go mod tidy

Configure gopls

If you use gopls, configure it to include the tools build tag, e.g., for VSCode, add the following to your settings.json:

"gopls": {
  "build.buildFlags": ["-tags=tools"],
}

Install globally

You can install the go.mod versions of all the tools to your $GOBIN directory (probably not the best idea, expand for details)
go install $(go list -f '{{join .Imports " "}}' tools.go)

Note that this can break things for you well outside the module that you run this in, so you're likely going to be better off installing specific versions of tools manually, at your discretion.

If you still feel like you want to have the tools built (they really do start faster that way), either of the following sections (on Makefile and bingo) might be right up your alley.

Makefile

Makefiles are very project-specific and everyone's setup is different, but I've found it useful to add this to my Makefile:

toolsGo := tools.go
toolsDir := bin/tools
toolPkgs := $(shell go list -f '{{join .Imports " "}}' ${toolsGo})
toolCmds := $(foreach tool,$(notdir ${toolPkgs}),${toolsDir}/${tool})
$(foreach cmd,${toolCmds},$(eval $(notdir ${cmd})Cmd := ${cmd}))

go.mod: ${toolsGo}
	go mod tidy
	touch go.mod

${toolCmds}: go.mod
	go build -o $@ $(filter %/$(@F),${toolPkgs})

tools: ${toolCmds}
.PHONY: tools

If you decided to put your tools.go in a tools package, you would have to change the toolsGo variable to tools/tools.go. It may also make sense to flip the toolsDir variable to tools/bin. And maybe put this snippet in a tools/tools.mk, that you could include to reduce the clutter in your main Makefile.

You'd probably want to add the toolsDir directory to your .gitignore.

The magic $(foreach ... $(eval ... line defines <toolname>Cmd variables (e.g. stringerCmd, mockgenCmd, etc.) to be used in other recipes in your Makefile. For (a very rudimentary) example:

wire: ${wireCmd}
	${wireCmd} ./...
.PHONY: wire

Running make wire would now

  1. run go mod tidy to ensure go.mod is up to date with tools.go (if the latter is modified more recently than the former)
  2. build wire at the version that's defined in your go.mod (if it's not already built), and, finally,
  3. recursively generate all your provider and injector wiring.

You can also execute

make tools

and, if go.mod is older than tools.go, it will run go mod tidy, after which all the tools that you have defined in tools.go will get built under the toolsDir (as bin/tools/stringer, for example).

3rd party: bingo

You can use bingo to install version-suffixed executables of your module's tools in the global $GOBIN directory. This is an entirely different approach that avoids conflicts among tools and tool dependencies in larger projects, but it doesn't integrate with your module's go.mod (which might also be a good thing).

The author introduced this tool in golang/go#25922 (comment) and further expanded on it in golang/go#25922 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment