Skip to content

fix: build a CGO-free CLI so companion publish links statically - #456

Merged
antoinetoussaint-byte merged 2 commits into
mainfrom
fix/companion-publish-static-cli
Aug 23, 2026
Merged

fix: build a CGO-free CLI so companion publish links statically#456
antoinetoussaint-byte merged 2 commits into
mainfrom
fix/companion-publish-static-cli

Conversation

@antoinetoussaint-byte

Copy link
Copy Markdown
Contributor

Summary

  • codefly companion publish (base + language images) cross-compiles the CLI with CGO_ENABLED=0 into bin/linux/$TARGETARCH/codefly, then COPYs it into the alpine-based companion images. That build could not link: pkg/engine/source.go unconditionally installed the tree-sitter semantic analyzer (core/code/semantic), which requires cgo, so CGO_ENABLED=0 go build failed with all tree-sitter bindings excluded by build constraints.
  • Split newSource by the automatic cgo build constraint: the cgo build keeps the analyzer (normal CLI behavior is unchanged); the !cgo build falls back to core's CGO-free default DefaultCodeServer. The companion CLI only builds/runs services in-container — it never serves the semantic gateway — so nothing of value is lost in that image.

Found while publishing the Go 1.27 companion image for codefly-dev/core#341: codefly companion publish go aborted before Docker ran, at the CLI cross-compile step.

Test plan

  • CGO_ENABLED=0 GOOS=linux go build -ldflags '-s -w -extldflags "-static"' .now succeeds (was exit 1: build constraints exclude all Go files for the tree-sitter bindings).
  • go build ./... (default, cgo on) — succeeds; the semantic analyzer is still linked into the normal CLI.
  • go vet ./pkg/engine/ passes both with cgo and with CGO_ENABLED=0.
  • gofmt clean.

🤖 Generated with Claude Code

…cally
The companion publisher cross-compiles the CLI with CGO_ENABLED=0 into
bin/linux/$TARGETARCH/codefly for the base + language images. That build
failed to link because pkg/engine unconditionally installed the tree-sitter
semantic analyzer (core/code/semantic), which requires cgo — so
`codefly companion publish` (and any static CLI build) errored out with
tree-sitter bindings excluded under CGO_ENABLED=0.
Split newSource by the cgo build constraint: the cgo build keeps the
analyzer (unchanged behavior for the normal CLI); the !cgo build uses
core's CGO-free default. The companion CLI only builds and runs services
in-container and never serves the semantic gateway, so it loses nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The static companion/self-cross build dropped the tree-sitter semantic
analyzer implicitly, by splitting newSource on the automatic `cgo` build
constraint (`//go:build cgo` / `!cgo`). That made CGO_ENABLED=0 a silent
switch: any build without cgo — including an *accidental* one, e.g. `go
install` on a machine with no C compiler, where Go auto-sets
CGO_ENABLED=0 — quietly produced a semantic-less CLI. Before this PR that
same build failed loudly ("build constraints exclude all Go files"),
which is the correct signal that a normal CLI needs cgo. Trading a loud
build failure for a silent capability downgrade is the exact "safe
default" that ships regressions.
Gate on an explicit intent tag instead:
- source_semantic.go //go:build !codefly_nosemantic (default; keeps
the analyzer; still fails to link under
CGO_ENABLED=0, restoring the loud signal)
- source_nosemantic.go //go:build codefly_nosemantic (CGO-free)
The two builds that legitimately want the analyzer-free variant now opt
in explicitly with `-tags codefly_nosemantic`: `codefly companion
build/publish` (buildLinuxCLI) and `codefly self build --os/--arch`
(buildCLICross). Renamed the files from _cgo/_nocgo to _semantic/
_nosemantic so the names track the real boundary, and named both opt-in
consumers in the CGO-free variant's doc (previously only companion was
mentioned).
Verification: add TestNewSourceExecutesLanguageNeutralOperation, which
asserts newSource returns a live Source whose base behavior runs; it
covers the analyzer variant in the normal suite and the analyzer-free
variant when run with -tags codefly_nosemantic. Wire a coverage-gate CI
step that builds the CGO-free variant statically and runs that test under
the tag, so a regression in the analyzer-free path is caught without the
Docker-dependent companions.yaml job.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@antoinetoussaint-byte
antoinetoussaint-byte merged commit ffcf19c into mainAug 23, 2026
5 checks passed
Sign up for freeto 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.

1 participant

@antoinetoussaint-byte