Skip to main content
Version: 2.23 (prerelease)

Go overview

Pants's support for Golang.


Go support is beta stage

We are done implementing the initial core functionality for Pants's initial Go support (tracked here). However, there may be some edge cases we aren't yet handling. There are also some features that are not yet supported like vendoring, which we'd love your input on how to prioritize!

Please share feedback for what you need to use Pants with your Go project by either opening a GitHub issue or joining our Slack!

Why use Pants with Go?

Go's builtin tooling is already excellent! Many projects may be fine only using Go's tooling, although Pants offers some unique benefits:

  • A consistent interface for all languages/tools in your repository, such as being able to run pants fmt lint check test package.
  • Integration with Git, such as running pants --changed-since=HEAD test.
  • Caching, such as caching test results on a per-package basis.
  • Remote execution and remote caching.
  • Advanced project introspection, such as finding all code that transitively depends on a certain package.
Example Go repository

Check out github.com/pantsbuild/example-golang to try out Pants's Go support.

Initial setup

First, activate the Go backend in pants.toml:

pants.toml
[GLOBAL]
backend_packages = ["pants.backend.experimental.go"]

You may want to set the option [golang].minimum_expected_version to a value like "1.17". Pants will use this to find a Go distribution that is the same version or newer. You still set your projects' Go version with go.mod with the go directive; this option is only used for Pants to discover a compatible Go distribution.

You can also set [golang].go_search_paths to influence where Pants looks for Go, e.g. ["/usr/bin"]. It defaults to your PATH.

Then run pants tailor :: to generate BUILD files. This will add a go_mod target where you have your go.mod file, a go_package target for every directory with a .go file, and a go_binary target in every directory where you have package main.

❯ pants tailor ::
Created BUILD:
- Add go_mod target root
Created cmd/deploy/BUILD:
- Add go_binary target bin
- Add go_package target deploy
Created cmd/runner/BUILD:
- Add go_binary target bin
- Add go_package target runner
Created pkg/deploy/BUILD:
- Add go_package target deploy
Created pkg/runner/BUILD:
- Add go_package target runner

Each go_package target allows you to set metadata for that directory, such as the test_timeout field. However, Pants uses sensible defaults so, usually, you can simply use what was generated by tailor.

The go_mod target generates a go_third_party_package target for each package belonging to the modules declared in your go.mod. You will rarely need to interact with these directly, thanks to dependency inference.

You can run pants list :: to see all targets in your project, including generated go_third_party_package targets:

❯ pants list
...
//:root#golang.org/x/net/ipv4
//:root#golang.org/x/net/ipv6
...
cmd/deploy:bin
cmd/deploy:deploy
cmd/runner:bin
cmd/runner:runner
pkg/deploy:deploy
pkg/runner:runner
go.mod and go.sum need to be up-to-date

Pants does not yet update your go.mod and go.sum for you; it only reads these files when downloading modules. Run go mod download all to make sure these files are correct.

The embed directive and resource targets

To use the embed directive, you must first teach Pants about the files with the resource / resources targets:

  1. Add a resource or resources target with the embedded files in the source / sources field, respectively.
  2. Add that target to the dependencies field of the relevant go_package target.

For example:

pkg/runner/BUILD
go_package(dependencies=[":embeds"])

resources(name="embeds", sources=["hello.txt"])
pkg/runner/lib.go
package runner

import _ "embed"

//go:embed hello.txt
var s string
print(s)
pkg/runner/hello.txt
Hello world!

Package and run binaries

To run a binary, use pants run path/to/main_pkg: (note the colon). You can pass through arguments with --, like this:

❯ pants run cmd/deploy: -- --help
Usage of /Users/pantsbuild/example/.pants.d/workdir/tmpzfh33ggu/cmd.deploy/bin:
--allow-insecure-auth allow credentials to be passed unencrypted (i.e., no TLS)
-A, --auth-token-env string name of environment variable with auth bearer token
...
pflag: help requested

You can also package your binaries (aka go build) by using pants package. package :: will build all your project's binaries, whereas package path/to/main_pkg: will build only the binary in that directory.

❯ pants package ::
[INFO] Wrote dist/cmd.deploy/bin
[INFO] Wrote dist/cmd.runner/bin

By default, Pants names the binary with the scheme path.to.directory/target_name, e.g. cmd.deploy/bin. You can set the field output_path to use a different name:

cmd/deploy/BUILD
go_binary(name="bin", output_path="deploy")

Compile code

To manually check that a package compiles, use pants check:

# Check this package
❯ pants check pkg/deploy:

# Check this directory and all subdirectories
❯ pants check pkg::

# Check the whole project
❯ pants check ::

(Instead, you can simply run package, run, and test. Pants will compile all the relevant packages.)

Run tests

To run tests, use pants test:

# Test this package
❯ pants test pkg/deploy:

# Test this directory and all subdirectories
❯ pants check pkg::

# Test the whole project
❯ pants test ::

You can pass through arguments with --, e.g. pants test pkg/deploy: -- -v -run TestFoo.

Loose files in tests (testdata)

To open files in your tests, use file / files targets and add them as dependencies to your go_package.

pkg/runner/BUILD
go_package(dependencies=[":testdata"])

files(name="testdata", sources=["testdata/*"])
pkg/runner/foo_test.go
package foo

import (
"os"
"testing"
)

func TestFilesAvailable(t *testing.T) {
_, err := os.Stat("testdata/f.txt")
if err != nil {
t.Fatalf("Could not stat pkg/runner/testdata/f.txt: %v", err)
}
}
pkg/runner/testdata/f.txt
"Hello world!"

Traditionally in Go, these files are located in the testdata directory. However, with Pants, you can place the files wherever you'd like. Pants sets the working directory to the path of the go_package, which allows you to open files regardless of where there are in your repository, such as with os.Stat("../f.txt").

Timeouts

Pants can cancel tests that take too long, which is useful to prevent tests from hanging indefinitely.

To add a timeout, set the test_timeout field to an integer value of seconds, like this:

BUILD
go_package(test_timeout=120)

You can also set a default value and a maximum value in pants.toml:

pants.toml
[test]
timeout_default = 60
timeout_maximum = 600

If a target sets its timeout higher than [test].timeout_maximum, Pants will use the value in [test].timeout_maximum.

Use the option pants test --no-timeouts to temporarily disable timeouts, e.g. when debugging.

Retries

Pants can automatically retry failed tests. This can help keep your builds passing even with flaky tests, like integration tests.

[test]
attempts_default = 3

Gofmt

Gofmt is activated by default when you activate the Go backend. Simply run pants fmt and pants lint:

# Format a single directory
❯ pants fmt cmd/deploy:

# Format this directory and all subdirectories
❯ pants fmt cmd::

# Check that the whole project is formatted
❯ pants lint ::

# Format all changed files
❯ pants --changed-since=HEAD fmt

If you'd like to disable Gofmt, set this:

pants.toml
[gofmt]
skip = true

To only run Gofmt, use --fmt-only and --lint-only:

❯ pants fmt --only=gofmt ::

golangci-lint

Pants can run golangci-lint on your Go source code. To activate, add this to your pants.toml:

pants.toml
[GLOBAL]
backend_packages = [
"pants.backend.experimental.go",
"pants.backend.experimental.go.lint.golangci_lint",
]

Now you can run pants lint:

$ pants lint main.go
20:39:43.10 [ERROR] Completed: Lint with golangci-lint - golangci-lint failed (exit code 1).
main.go:5:6: func `bad` is unused (unused)
func bad() {
^



✕ golangci-lint failed.

Pants will automatically include any relevant .golangci.yml, .golangci.yaml, .golangci.json, or .golangci.toml files in the run. You can also pass command line arguments with --golangci-lint-args='--tests --fast' or permanently set them inpants.toml`.

[golangci-lint]
args = ["--fast", "--tests"]

Temporarily disable golangci-lint with --golangci-lint-skip:

pants --golangci-lint-skip lint ::

Only run golangci-lint with --lint-only:

pants lint --only=golangci-lint ::
Benefit of Pants: golangci-lint runs in parallel with other linters

Pants will attempt to run all activated linters and formatters at the same time for improved performance, including Python, Shell, Java, and Scala linters. You can see this through Pants's dynamic UI.