skip to content

A build-constrained Go file compiles in no configuration at all. How do you find which files a build actually includes, and why yours is missing?

level: seniorimportance: should knowfreq 34%

answer

  1. ask the tool, do not read comments
  2. list the files in and the files out
  3. in neither list is a different bug
  4. the name and the header are ANDed
  5. a mistyped tag never errors

basics

~20 s

Ask the go command instead of reading comments: go list -f '{{.GoFiles}}' prints the files in the build and go list -f '{{.IgnoredGoFiles}}' prints those a constraint excluded. Re-run it with the tags and target you expect, then check the constraint, the filename and the placement.

solid answer

~50 s

Stop reading constraint comments and query the build. `go list -f '{{.GoFiles}}' ./pkg` prints exactly the files the current configuration compiles, and `go list -f '{{.IgnoredGoFiles}}' ./pkg` prints the ones a build constraint excluded — if your file appears in neither, it is not being read as part of the package at all, which usually means the name starts with `_` or `.`. Re-run with `-tags` and with the target OS and architecture you expect, and the difference tells you which input decides. Then check the four usual causes: an expression that cannot be satisfied, such as `//go:build linux` on a file named `handler_darwin.go`, since the filename constraint and the header are ANDed; a directive below the package clause or written `// go:build` with a space, which `go vet`'s buildtag check reports; a mistyped tag name, which never errors because tags are arbitrary identifiers; and a filename suffix that is not a real GOOS or GOARCH.

code

text · 8 lines
text
$ go list -f '{{.GoFiles}}' ./storage
[storage.go storage_default.go]

$ go list -f '{{.IgnoredGoFiles}}' ./storage
[storage_integration.go storage_plan9.go]

$ go list -f '{{.GoFiles}}' -tags integration ./storage
[storage.go storage_integration.go]

go deeper

for a junior

Know that a file can be excluded silently, and that go list -f '{{.GoFiles}}' shows you which files a build really contains rather than guessing from comments.

for a middle

Explain the mechanics behind each cause: name and header are ANDed, a directive below the package clause is a plain comment, and unknown tag names are never errors.

for a senior

Walk the diagnosis end to end — the two listings, the tag and target variations, and the four causes — and treat an uncompiled file as untested, unvetted dead code rather than a harmless leftover.

for a principal

Own the standard that every file has a configuration someone actually builds, and make constraint changes reviewable by requiring the before-and-after file listing instead of a reading of the comment.

## Why this failure is worth a method A file excluded by a build constraint is not compiled, so it is not type-checked, not vetted, and not covered by tests. It can reference functions that were deleted a year ago and nothing will say so. Worse, the symptom that eventually surfaces is usually somewhere else: an undefined symbol in a file that expected a platform partner, or a feature that quietly does nothing. So the question is never "is this constraint right" read by eye — it is "which files are in this build", answered by the tool. ## The one command that settles it `go list` reports the package as the go command sees it, with the constraints already applied: ``` $ go list -f '{{.GoFiles}}' ./storage [storage.go storage_default.go] $ go list -f '{{.IgnoredGoFiles}}' ./storage [storage_integration.go storage_plan9.go] ``` `GoFiles` is the set that will be compiled. `IgnoredGoFiles` is the set that exists on disk and was excluded by a build constraint. Three outcomes, three diagnoses: - **In `GoFiles`** — the constraint is not the problem; go look at the symbol. - **In `IgnoredGoFiles`** — the constraint is being evaluated and is false. Now find out which input is wrong. - **In neither** — the go command is not treating it as a package source file at all. The overwhelmingly common cause is a leading `_` or `.` in the name; the file is skipped before constraints are even considered. Add `-tags` to see the effect of a tag set, and set the target OS or architecture in the environment to ask the same question about another platform. Diffing the two listings is the proof; reading the comment is a guess. ## The four causes, in the order they bite **1. The expression is unsatisfiable.** The filename constraint and the `//go:build` line are combined with AND. `handler_darwin.go` whose header says `//go:build linux` is compiled by nothing, and neither the compiler nor the linker will ever mention it. The same happens with a hand-written `//go:build linux && windows`. This is the classic aftermath of a rename: the file moved platforms, the header did not. **2. The directive is inert.** A `//go:build` line below the package clause is an ordinary comment. So is `// go:build`, with a space after the slashes. Both are invisible in review because they look exactly right. `go vet` ships a `buildtag` analyzer that reports misplaced and malformed constraints, and running vet over the tree is the cheapest possible standing guard against this. **3. The tag name is wrong.** There is no registry of tags, so `-tags integraton` is a perfectly legal build that simply does not set the tag you meant, and a header saying `//go:build intergration` compiles in no configuration you will ever run. Nothing errors. The listing diff catches it instantly: the file stays in `IgnoredGoFiles` even with the flag you thought turned it on. **4. The filename suffix is not a target.** `service_prod.go` has no constraint at all — the opposite failure, a file that is always compiled when its author expected it to be selective. Only real GOOS and GOARCH values count as suffixes. ## Turning the diagnosis into a habit The deeper problem is that the default build is the only configuration most people ever compile, so every other configuration decays. Two habits fix most of it: - **Every file must have a named configuration in which it compiles.** If nobody can say which one, that is a review comment, not a curiosity. A file with a constraint and no partner file, or no caller in any configuration, is dead code with camouflage. - **Keep per-platform and per-tag file sets symmetric.** If the tagged file defines three names, the untagged one defines the same three. Then a missing definition is a compile error in one configuration rather than a mystery in another. And when reviewing a change to a constraint line, ask for the `go list` output before and after. It is two commands, it is unambiguous, and it is the only artefact that proves what the build now contains. ## The newcomer's version of the same problem Someone new to the codebase reads a function in `storage_darwin.go`, sets a breakpoint, and it never fires — because they are on Linux and the definition they are reading is not in their build. The orientation fix is structural: keep the platform-independent entry points in an unsuffixed file, so there is one obvious place where the reader starts and the dispatch to a per-platform definition is visible from there.

  • Your file appears in neither `GoFiles` nor `IgnoredGoFiles`. What does that tell you?
    That the go command is not treating it as a source file of the package at all, rather than excluding it by constraint. The usual cause is a name beginning with `_` or `.`, which the go command skips before constraints are considered. A wrong directory or a file whose name lacks the `.go` extension produces the same silence.
  • Which check catches a `//go:build` line that is inert because of where or how it is written?
    `go vet`'s buildtag analyzer. It reports constraints placed after the package clause and malformed constraint lines, which the compiler will never mention because to it they are ordinary comments. It is the standing guard for the class of mistake that looks correct in review.
  • Why does a review of a constraint change deserve the `go list` output rather than a reading of the line?
    Because the line is only one of the inputs. The filename adds its own constraint, the two are ANDed, and the tag set differs between the author's machine and everyone else's. The file listing before and after is the only artefact that states what the build actually contains, and it takes two commands to produce.

saying these in an interview costs you the question

  • Reads constraint comments instead of listing the files
  • Assumes a compile error would reveal an excluded file
  • Forgets the filename adds its own ANDed constraint
  • Expects a mistyped tag name to produce an error
  • Never checks any configuration but the default one