Help us shape the future of Go! Take the 2026 Go Developer Survey

buildscan

command
v0.68.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2026 License: BSD-3-Clause Imports: 14 Imported by: 0

Documentation

Overview

Command buildscan answers two questions a green CI cannot: which build configurations does a repository's CI actually compile, and is there source in the tree that none of them reaches.

buildscan                  # the whole fleet, under the GitHub root
buildscan -root DIR        # somewhere else
buildscan -repo o/r        # one repository, verbosely
buildscan -vet             # also COMPILE: go vet under each configuration
buildscan -all             # include the repositories that are fine
buildscan -fail            # exit 1 if anything is found

Why

A file behind a build constraint is not compiled by a `go test` that does not select it, so every statement a native run makes about it is a statement about a configuration the file is not in. Two failures here came from that, days apart and in opposite directions:

  • go-crdt/collab, 2026-10-11. A new test file in the root package had no constraint at all, and the helpers it calls live in a file that is `!js`. `go test ./...` passed on this machine and the js/wasm lane failed to compile -- `undefined: serve`. The lane was the only witness, and it answers in eight minutes rather than in one second.
  • go-compressions/blake3, 2026-10-07. `blake3_simd_arm64.go` is behind `goexperiment.simd && arm64`: the job that sets the experiment is amd64, and the only arm64 workflow says in its own header "no GOEXPERIMENT". Each half compiled by some lane, the conjunction by none. The file does not build, and the CI had been green since the day it was written, because no configuration that could fail existed. It was found by running `GOEXPERIMENT=simd go test ./...` by hand on an arm64 machine.

Mutation sweeps make the second kind visible from the other side: a refusal in a file no configuration compiles can only ever be reported SURVIVED, since the mutant is never built. That is how the browser half of go-crdt's sweep came to be swept at all.

This is the single-configuration blind spot that the product-line literature names: Kästner et al.'s variability-aware analysis type-checks ALL configurations of Busybox's 811 options without the brute-force product, and its motivation is exactly that per-configuration tools answer about one variant and are read as answering about the program. Go's own tools have the same gap -- golang.org/x/tools/cmd/toolstash/buildall builds std for every GOOS/GOARCH, and nothing does it for a repository's declared set.

What it reads, and what it cannot

It reads the working tree: every .go file's constraints (through go/build, so a `_windows.go` name counts as much as a `//go:build` line), and every `go test|build|vet|install|run` COMMAND in .github/workflows, with the strategy matrix expanded.

Per command rather than per step, because a step holding two of them declares two configurations -- which this tool got wrong at first, and which using it is what found: a lane written as one block with `GOOS=darwin GOARCH=arm64 go vet ./...` and the amd64 line under it was reported as one configuration, and the workflow was rewritten as two steps to work around a limitation its reader should never have had to carry.

Four things it cannot resolve, each reported rather than guessed:

  • a GOOS or GOARCH that comes from somewhere outside the file -- a shell variable, an expression, a value computed in an earlier step;
  • a self-hosted runs-on label, which says nothing about its platform: this fleet has arm64 self-hosted runners whose labels do not say so;
  • a workflow that hands the work to a script or a Taskfile, whose contents are not in the workflow;
  • a workflow the YAML parser refuses, which is reported as unreadable rather than silently contributing nothing.

Two deliberate biases, both towards saying LESS:

  • `exclude:` in a matrix is not applied, and cgo is assumed enabled. Both make the CI look like it covers more. A file wrongly named as compiled by nobody sends somebody to read a file that is fine; a file wrongly left out of that list is the silence this tool exists to break, so the error is taken in the direction that cannot invent work.
  • `_test.go` files count. A test nothing compiles is the collab failure above, and it is the common case: test files are where new constraints are forgotten.

-vet

`-vet` stops asking and compiles: for each configuration, `go vet ./...` in the module that configuration's step runs in, with GOOS, GOARCH and -tags set to it. That is the check that would have caught the collab failure in the second it takes, rather than in the eight minutes a lane takes. It is off by default because a fleet-wide run of it is hundreds of cross-compilations.

⛔ It COMPILES the tree, which means it builds the dependencies that tree names and may fetch them. The scan without it reads files and makes no API call at all -- one local `git` for each checkout that has no workflows, to tell a repository missing a lane from a tree with no origin, and nothing else; `-vet` does not have that property, and pointing it at a checkout somebody else controls is the same decision as building that checkout. GOOS, GOARCH and the tags reach `go` as arguments and environment values, never through a shell, and a `working-directory` that leaves the checkout -- absolute, or climbing out with `..` -- is refused rather than followed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL