How do go modules work with installable commands?

If you get an error

I was not seeing the dependency that I wanted added to the go.mod and I was getting this error:

internal/tools/tools.go:6:5: import "github.com/UnnoTed/fileb0x" is a program, not an importable package

(fileb0x is the thing I'm trying to add)

I'm not 100% clear on the sequence of events that fixed it, but I did all of these things:

Using a "tools" package

I made a tools directory:

mkdir -p internal/tools

I put the tools package inside of it (as mentioned above):

internal/tools/tools.go:

// +build tools

package tools

import (
    _ "github.com/UnnoTed/fileb0x"
)

Note that the tag is mostly not important. You could use foo:

// +build foo

However, you cannot use ignore. That's a special predefined tag.

// +build ignore

// NO NO NO NO NO
// `ignore` is a special keyword which (surprise) will cause
// the file to be ignore, even for dependencies

Updating go.mod

The best way is probably to run go mod tidy:

go mod tidy

However, before I did that I ran a number of commands trying to figure out which one would cause it to go into go.mod:

go install github.com/UnnoTed/fileb0x # didn't seem to do the trick
go get
go generate ./...
go build ./...
go install ./...
go mod vendor

Later I did a git reset and rm -rf ~/go/pkg/mod; mkdir ~/go/pkg/mod and found that go mod tidy did well enough on its own.

vendoring

In order to actually take advantage of the modules cache in a project you need to copy-in the source code

go mod vendor

That will grab all dependencies from go.mod

You also need to change nearly all of your go commands to use -mod=vendor in any Makefiles, Dockerfiles or other scripts.

go fmt -mod=vendor ./... # has a bug slated to be fixed in go1.15
go generate -mod=vendor ./...
go build -mod=vendor ./...

That includes go build, go get, go install, and any go run called by go generate (and even the go generate itself)

//go:generate go run -mod=vendor github.com/UnnoTed/fileb0x b0x.toml
package main

// ...

https://github.com/golang/go/issues/25922 proved helpful for me, especially

when using build-only dependencies with modules the main point is version selection (not installing these!)

To avoid installing you can modify your //go:generate directive to something like:

//go:generate go run golang.org/x/tools/cmd/stringer ARGS

There is also the best practices repo: https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md


The convention is to add a file named "tools.go" that is guarded by a build constraint and imports all required tools:

// +build tools

package tools

import (
    _ "github.com/aprice/embed/cmd/embed"
)

https://github.com/golang/go/issues/25922#issuecomment-412992431

The tools are then installed as usual in one of

  • $GOBIN
  • $GOPATH/bin
  • $HOME/go/bin

You may also want to follow https://github.com/golang/go/issues/27653, which discusses future explicit support for tools.

Tags:

Go