Skip to content

build: tools are built for the host, only the product follows the target - #653

Merged
David-Noble-at-work merged 1 commit into
developfrom
host-toolchain-pin
Aug 24, 2026
Merged

David-Noble-at-work merged 1 commit into
developfrom
host-toolchain-pin

Conversation

@David-Noble-at-work

Copy link
Copy Markdown
Contributor

GOOS/GOARCH are ambient, so they reached every go invocation in the build — including the ones
that build and run the generators. Cross-compiling the product cross-compiled the toolchain that
generates its sources, and the host then tried to execute a foreign binary.

Closes #639. Unblocks #598.

The fix

HOST_GO pins GOOS/GOARCH to go env GOHOSTOS/GOHOSTARCH. Explicit rather than clearing the
variables, so a recipe using it says why it differs from a plain go. When the target is the host
these expand to the ambient values, so a native build is byte-for-byte what it was.

Three tool sites, not the two the issue named:

  1. star (Makefile:141) — the generator every codegen recipe invokes as $(STAR).
  2. inventory (Makefile:549-550) — go run builds and executes in one step.
  3. gocyclo (Makefile:246) — found while fixing this. go install under a cross-GOOS lands the
    binary in GOPATH/bin/<goos>_<goarch>/, so the -x probe fails and make complexity breaks. Same
    defect class, and it would have outlived the fix.

A second defect underneath it

With the tools pinned, codegen completed and the build failed further along — at the version-stamp
check, which executes the freshly built build/writ to confirm -X bound. That guard exists
because -X against a non-existent symbol is silently ignored, and every release before 2026-08-16
shipped unstamped, unnoticed.

It cannot run a foreign binary, so it is now skipped when CROSS is non-empty — and says so:

cross-compiling for linux/arm64: skipping the version-stamp check (cannot run a linux binary on darwin)

A skipped guard that prints nothing reads exactly like a passing one. It still runs, unchanged, on
every native build.

The path collision, and why it is not a bug

build writes the product star to build/star$(GOEXE) — on a non-Windows target, the same path as
the host tool. A cross-build therefore leaves a target-architecture binary at build/star.

That self-heals: build/star: FORCE delegates to make star on every run, so the next codegen
rebuilds it host-side before invoking it. Verified, not assumed — cross-built for linux (leaving an
ELF at build/star), forced codegen, and watched 25 star invocations succeed with build/star back to
Mach-O. Recorded in the Makefile comment; #598's build/<goos>-<goarch>/ layout removes the collision
outright.

Verification

Result
make build (native) 0
GOOS=linux make build 0 — was exec format error
GOOS=windows GOARCH=arm64 make build 0 — was exec format error
Second consecutive build, native and linux zero codegen invocations (#572's property, preserved)
make test 103 ok / 0 fail
make vet darwin / linux / windows clean
make complexity, make shell-lint 0
gofmt -l clean

Not fixed here, and correct as-is: make install runs the built binaries, so it fails under a
cross-compile. Installing a foreign binary on this host is not a meaningful request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cross-compiling the product cross-compiles the build tools too

1 participant