February 16, 2018·6 min read·engineeringgobuildstesting

Go 1.10 makes build reuse independent of installed packages

Go 1.10 adds a content-based build cache and reuses successful package tests. Their different invalidation rules change what repeated commands mean.

Changing a compiler flag should not require remembering to force a rebuild. Returning to an earlier source tree should not require installing its dependencies again to recover a fast development cycle. Go 1.10 addresses both problems by changing how the go command decides whether previous work remains useful. Brad Fitzpatrick's release announcement, published today, singles out automatic build and test caching as a major change.

The two caches answer different questions. A build result records work that produced a compiled package. A test result records a successful execution of a particular test binary under particular conditions. Reusing the first still allows the tests to run. Reusing the second means displaying output from an earlier execution. Understanding that separation is necessary to interpret the new behavior correctly.

I value Go's tendency to put common development decisions in one tool. A cache managed by the go command can remove project-specific instructions about installing dependencies and forcing recompilation. The implementation still needs rules that a programmer can inspect when reuse is surprising.

Source content replaces timestamp inference

The Go 1.10 release notes explain that build freshness now depends on source contents, build flags, and metadata in compiled packages. File modification times no longer decide whether a package needs rebuilding. That removes a mismatch between the question a build system needs to answer and the information a timestamp provides.

A timestamp tells the tool when a filesystem entry was last modified. It does not describe the bytes currently in the file. Restoring a file's earlier contents can produce a new timestamp while leaving the desired compilation result unchanged. Conversely, a changed build flag can require different compiler output even when no source file has changed. The new rules make these distinctions part of the build system's normal operation.

The implications extend to switching between copies of a source tree. The release notes explicitly identify this as a case the cache should help. A compiled result can remain useful when the corresponding source contents reappear. A separate cache gives the go command a place to retain that result without requiring the package to remain installed in the workspace's package directory.

This is a change in responsibility for the developer. Advice to use -a because modification times might mislead the builder is no longer the ordinary remedy. The tool now claims to account for the inputs that determine the Go compilation. A failure to rebuild after a relevant Go input changes is a bug in that claim, rather than an expected inconvenience for callers to work around.

Installation no longer has to maintain the build cache

The new cache is separate from the installed package files under GOROOT/pkg and GOPATH/pkg. Installation and reuse therefore stop requiring the same operation. The versioned go command documentation describes the cache location and the controls available to a caller.

The practical distinction is easy to overlook. An installed executable is an output requested by a user. A cached compiled package is an intermediate result that may accelerate a future command. Their lifetimes need not match. Deleting an intermediate result should cost rebuilding time without changing which executable the user asked to install.

Go 1.10 consequently changes go install to install the packages or commands explicitly named on its command line. It does not ordinarily install their dependencies. Those dependencies can still contribute cached results. The previous performance advice to add -i to builds or tests is no longer necessary for this purpose.

Cache management also has a defined location. go env GOCACHE prints the selected directory, and the GOCACHE environment variable can override the default. The command periodically removes old cached data. These rules make the cache a managed part of the toolchain, rather than another project directory whose contents a build script must maintain.

A successful test result needs more evidence

Test-result reuse is narrower. Go 1.10 caches successful tests in package-list mode. A bare go test in the current directory and an invocation that explicitly names packages do not have identical caching behavior. The distinction makes the command line part of the test's meaning.

These commands illustrate the relevant forms for an existing package tree.

go test
go test .
go test ./...
go test -count=1 ./...

The first uses local-directory mode. Both middle commands explicitly select packages and can reuse successful results when the cache's conditions hold. The final command requests an execution without test-result reuse. It can still reuse compiled build outputs, which is useful when the requirement is to rerun tests without needlessly rebuilding their dependencies. Naming a package thus affects execution policy even when the selected tests happen to be exactly the same ones.

The versioned documentation defines a match in terms of the same test binary and permitted test flags. In this release the cacheable flag set includes -run and -short, alongside output and execution controls such as -v and -parallel. Flags outside the documented set can prevent reuse. When a cached result supplies the output, the summary displays (cached) instead of an elapsed time.

Only successes enter this mechanism. A failed run does not become an answer that the command keeps returning without another execution. This asymmetry suits ordinary development because a previously passing result can avoid work while a failure still needs another opportunity to reflect a correction or a changed condition.

The cache also accounts for certain runtime inputs. The documentation describes consulted environment variables and files opened within the package's source root. If these tracked inputs change, the earlier successful execution does not match. This extends the reuse decision beyond whether the test executable's bytes changed.

External state remains a separate concern

A test that contacts a running service depends on state that is not described by its own compiled binary. An unchanged environment variable naming that service does not prove that the service still has the same data. This is an inference from the documented cache inputs. It explains why an integration test can require another execution even when the go command has a valid cached success under its own rules.

I regard -count=1 as an explicit statement about that requirement. It says that the execution itself is wanted. Using it for tests of external state preserves the useful build cache while asking for a fresh observation of the system under test.

Native dependencies introduce another documented boundary. The cache does not detect changes in C libraries imported through cgo. The go command's help therefore retains a reason to clear the build cache or force recompilation when those libraries change. Content-based Go builds do not imply that the tool observes every mutable input supplied by the surrounding operating system.

These exceptions concern different cached products. An external service may make a previous test success unsuitable without requiring any recompilation. An updated C library may require rebuilding a package even before the question of test-result reuse arises. Treating both situations as a generic stale cache makes it harder to choose the operation that addresses the actual dependency.

The tool exposes separate controls

The versioned help provides go clean -cache for compiled results and go clean -testcache for successful test results. Their separation is useful documentation of the design. Invalidating test history need not remove the compiler work that makes the next execution inexpensive.

There are diagnostic controls as well. GODEBUG=gocachetest=1 reports test-reuse decisions. The hash diagnostic reports inputs to cache keys, while verification mode rebuilds and compares results with existing entries. These mechanisms give a developer evidence about what the tool considered, without making routine cache deletion the normal development cycle.

The release also runs a selected set of vet checks during go test. A successful test command consequently includes more than a completed test binary execution. Certain static findings can stop the command before tests run. That behavior belongs in the interpretation of the command's result even when caching is the most visible speed improvement.

Go 1.10 keeps the usual build and test commands while changing the work those commands need to repeat. The practical benefit comes from ordinary invocations retaining enough information to reuse earlier results. For a package with deterministic local tests, a second explicit package test can report a cached success. For a test whose purpose is to query changing external state, -count=1 asks for the execution while leaving reusable compilation results available.