buildinfo

package
v1.2.11 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package buildinfo answers one question in one place: WHICH BUILD IS THIS.

Every binary in this repo can be running a different build at the same moment -- a release binary somebody downloaded, a `go install` from a tag, a working tree with three uncommitted edits -- and the first question after any of them misbehaves is which. Five binaries answered it with five copies of the same forty lines, and a copied answer is an answer that drifts: the deleted nova-wake tool's copy had already lost the build time and the dirty marker, so two lines comparing a `nova-bus version` against a `nova-wake version` were comparing two different spellings of the same fact.

So the resolution order lives here, once, and the one line every `version` verb prints is built here too. The order:

-ldflags "-X main.version=..."  what the release workflow stamps: the tag, exactly
the module version              what `go install ...@v1.2.3` records for itself
the vcs stamp                   <utc revision time>-<12 hex of the revision>[-dirty]
devel                           a build with none of the above, SAYING it has none

The middle two come from debug.ReadBuildInfo, which the toolchain fills in with no help from any file here: there is nothing to remember to update and therefore nothing to forget. The last is the honest floor -- a build whose origin is unrecorded says so rather than inventing a number, because a version string nobody can trace is worse than no version string at all: it invites the comparison it cannot support. NOTHING HERE EVER INVENTS A DOTTED NUMBER.

ONE LINE, FOUR TOKENS AND THEN NAMED EXTRAS. Every field of Line goes through oneline.Field, so what is printed is whitespace-separated tokens whatever the -X held -- and the -X value is the one field in the whole line that comes from outside the toolchain. A tool with more to say says it as `key=value` after the fourth token, and Parse, here, is the one reader of the whole shape: writer and reader are one pair, so a tool that adds a fact cannot break a consumer that has never heard of it.

Index

Constants

View Source
const Unknown = "devel"

Unknown is what a build with no stamp and no vcs information reports. It is the same spelling the Go toolchain uses for an uninstalled build, so it reads as familiar rather than as a bug in the tool printing it.

Variables

This section is empty.

Functions

func Line

func Line(tool, stamped string, extras ...string) string

Line is the one line a `version` verb prints, without its newline:

<tool> <build identity> <goos>/<goarch> <go version>

Four tokens, in this order, from every binary in the set -- so a release assertion, or a person holding two pastes, reads the identity out of field two and never has to know which tool wrote the line. Every field is rendered through oneline.Field, including the tool's own name, because a helper that escapes three of four fields is a helper whose guarantee has to be re-checked at every call site.

A tool with one more true thing to say about itself says it as an EXTRA: a `key=value` token after the fourth -- `build=<12 hex>` from nova-merge, `backend=` and `platform=` from nova-sandbox. Extras are part of the grammar rather than exceptions to it. On 2026-09-18 nova-merge's hand-rolled fifth token made `nova-version snapshot` refuse an entire install, because each reader had been written against the four tokens it happened to know. So the writer takes extras HERE, where Parse is guaranteed to read them back, and REFUSES one that is not key=value: a writer looser than its reader is a refusal deferred to whoever runs the snapshot.

func Resolve

func Resolve(stamped string, info *debug.BuildInfo, ok bool) string

Resolve takes the stamp and the build information as ARGUMENTS rather than reading them, so that the order above is testable: a test cannot install itself from a module proxy or rebuild itself from a dirty tree, and an order asserted only by the build it happens to run under is asserted by one case out of four.

func Version

func Version(stamped string) string

Version is Resolve over the calling binary's own build information. The stamped argument is the caller's `var version string`, which only a release's -ldflags "-X main.version=<tag>" ever writes: -X can only reach a string var in the main package, so the var stays there and the reading of it happens here.

Types

type Fields

type Fields struct {
	Tool      string   // field one: the binary's own name for itself
	Version   string   // field two: the build identity, the one field a comparison reads
	Platform  string   // field three: <goos>/<goarch>
	GoVersion string   // field four: the toolchain that built it
	Extras    []string // every key=value token after the fourth, in the order printed
}

Fields is one version line taken apart. It is what every READER of a version line in this tree gets, so that "what a version line is" is answered in one place rather than once per consumer: nova-version's snapshot and its report both ask Parse, and a tool that adds an extra tomorrow is already legible to both.

func Parse

func Parse(s string) (Fields, bool)

Parse reads the first line of what a `version` verb printed and reports whether it is a version line at all. The grammar, stated once for the whole tree:

<tool> <build identity> <goos>/<goarch> <go version> [key=value ...]

It is deliberately strict about the four mandatory tokens -- a caller uses ok to tell "this binary is broken" from "this binary is old", and a parser that accepts a usage refusal can tell neither -- and deliberately open about what follows, because the hurt it exists to end was a reader taking one tool's extra fact for a broken build. Field three is exactly one <goos>/<goarch> pair as docs/SPEC.md spells the grammar: one separator with nonempty components, the same shape fleet's validPlatform already requires of this field, so the shared parser and its consumers agree. The components are opaque names, never a fixed allowlist, so a valid cross-platform line still parses.

func (Fields) Extra

func (f Fields) Extra(key string) (string, bool)

Extra is the value of one named extra. A reader asks for the fact it wants BY NAME and never holds a position, so a tool that adds a second extra cannot move the first.

func (Fields) FindSource

func (f Fields) FindSource() (Source, bool)

FindSource returns the Source the fields carry, or false. The four source keys are the only ones FindSource reads: anything else in the extras -- nova-merge's `build=<hex>`, nova-sandbox's `backend=` -- is ignored. A version line that carries none of the four is reported with ok=false: old binaries, foreign tools, and a `go install` from a tag never had this, and "no Source" is the honest answer. A version line that carries SOME but not ALL of the four is ALSO reported with ok=false, and so is a contradictory one (below); PartialSource tells those apart from "none" so the snapshot can note them. Nothing is refused here: the reader never guesses at a missing field, and the caller decides what a source it cannot read costs.

A malformed dirty token (anything other than "true" or "false") is refused: a value like `dirty=maybe` is not a clean source and must not be silently accepted as dirty=false. Duplicate source keys are also refused: two `repo=` entries in the same line are a contradiction the reader cannot resolve.

func (Fields) PartialSource

func (f Fields) PartialSource() bool

PartialSource reports whether the line named source but FindSource could not read it whole: it carries at least one of the four keys and yet FindSource returned ok=false, whether for a missing key, a repeated key or a malformed dirty (SPEC-VERSION item 6). A line with none of the four, and one FindSource reads, are both false.

type Source

type Source struct {
	// Repository is the checkout the build came from, e.g. "github.com/owner/repo". A
	// build from a different repository cannot pass even if the linker stamp matches.
	Repository string
	// Revision is the full SHA the build was cut at, or its 12-hex prefix. Two builds
	// of two adjacent commits already disagree on this field, and a binary that names
	// the wrong revision is a different build.
	Revision string
	// Dirty is true when the source tree carried uncommitted changes at build time. A
	// release binary whose tree was dirty at the build is not the commit it names, and
	// this field is what a reader verifies it against.
	Dirty bool
	// BuildHost is the hostname the build ran on. Two hosts cutting the same commit
	// produce the same stamp but different artifacts, and this field is what tells
	// them apart.
	BuildHost string
}

Source is the structured view of WHERE a binary was built from. The version line has always carried an unstructured "the stamp this build reports"; Source is the four-field shape the snapshot reads off that line to check that every binary which names a source names the same one (a different repository, revision, dirty flag or build host is refused, SPEC-VERSION item 6). Only the snapshot reads it today: apply --sha's postflight and `moved`'s readback do not, and the gate checks consistency across the binaries' own claims, not the build, so a binary that states the wrong source consistently is not caught by it.

All four fields are always written together, so the round-trip is unambiguous: a Source the reader can extract is one the writer wrote whole. A version line that carries some but not all of the four is read as "no source", and PartialSource says so, so the caller can name it rather than treat it as silent.

func (Source) Extras

func (s Source) Extras() []string

Extras returns the Source as a slice of key=value tokens, the shape Line takes as its variadic extras. The four tokens appear in a fixed order -- repo, revision, dirty, build_host -- so the writer and the reader cannot disagree about which field is which. Dirty is ALWAYS emitted (true OR false), because a Source the reader can extract is one the writer wrote whole, and a missing dirty field is a Source the reader is forced to refuse.

Jump to

Keyboard shortcuts

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