fix(backend): set LC_ALL=C for CGO Make targets under Git Bash on Windows (#2720)
Co-authored-by: Joao Manso <X363081@bcpcorp.net> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
This commit is contained in:
+36
-11
@@ -111,6 +111,29 @@ else
|
|||||||
endef
|
endef
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# CGO locale for Git Bash/MSYS on Windows.
|
||||||
|
#
|
||||||
|
# This mingw (WinLibs) gcc resolves its own install path, then passes it to ld/
|
||||||
|
# collect2 across a locale-sensitive boundary. Under a UTF-8 locale, ld mis-decodes
|
||||||
|
# the ANSI-encoded path, so a non-ASCII segment (e.g. an accented Windows
|
||||||
|
# username under C:\Users\...) collapses to U+FFFD and the link cannot find
|
||||||
|
# crt2.o / crtbegin.o / -lm. Forcing the C locale keeps the raw path bytes, so the
|
||||||
|
# CGO link resolves. See docs/public/windows-support.md.
|
||||||
|
#
|
||||||
|
# Use LC_ALL=C, not LANG=C: LANG is the lowest-precedence locale variable
|
||||||
|
# (LC_ALL > LC_CTYPE > LANG), so LANG=C is silently ignored when the shell already
|
||||||
|
# exports LC_ALL or LC_CTYPE as UTF-8. LC_ALL=C overrides them all.
|
||||||
|
#
|
||||||
|
# Scope mirrors the shell-syntax block above: Windows but NOT native cmd, i.e.
|
||||||
|
# $(MSYSTEM) is set, so this is Git Bash/MSYS. It is empty only on Unix and native
|
||||||
|
# cmd. On Git Bash/MSYS the prefix is always applied: a harmless no-op on an ASCII
|
||||||
|
# toolchain path, and the actual fix on a non-ASCII one.
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
ifneq ($(OS)$(MSYSTEM),Windows_NT)
|
||||||
|
CGO_LOCALE := LC_ALL=C
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
# Build info
|
# Build info
|
||||||
#
|
#
|
||||||
# Keyed on $(OS)$(MSYSTEM) like the shell-syntax block above. cmd.exe has no
|
# Keyed on $(OS)$(MSYSTEM) like the shell-syntax block above. cmd.exe has no
|
||||||
@@ -220,18 +243,20 @@ build-runtime: build-agentctl build-agentctl-remote build-kandev
|
|||||||
build-kandev:
|
build-kandev:
|
||||||
@echo "Building $(BINARY_NAME)..."
|
@echo "Building $(BINARY_NAME)..."
|
||||||
@$(call MKDIR,$(BUILD_DIR))
|
@$(call MKDIR,$(BUILD_DIR))
|
||||||
$(CGO_PREFIX) $(GO) build $(GOFLAGS) -tags fts5 -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)$(EXE) ./cmd/kandev
|
$(CGO_LOCALE) $(CGO_PREFIX) $(GO) build $(GOFLAGS) -tags fts5 -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)$(EXE) ./cmd/kandev
|
||||||
|
|
||||||
## Build all binaries (unified + individual services + agentctl)
|
## Build all binaries (unified + individual services + agentctl)
|
||||||
build-all: build build-agentctl
|
build-all: build build-agentctl
|
||||||
@echo "Building individual services..."
|
@echo "Building individual services..."
|
||||||
$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/agent-manager$(EXE) ./cmd/agent-manager
|
$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/agent-manager$(EXE) ./cmd/agent-manager
|
||||||
|
|
||||||
## Build the agentctl sidecar binary (includes embedded MCP server)
|
## Build the agentctl sidecar binary (includes embedded MCP server). Carries
|
||||||
|
## $(CGO_LOCALE) because agentctl imports cgo (unlike mock-agent/acpdbg/winjob),
|
||||||
|
## so its native `go build` invokes gcc; see the CGO_LOCALE note above.
|
||||||
build-agentctl:
|
build-agentctl:
|
||||||
@echo "Building agentctl..."
|
@echo "Building agentctl..."
|
||||||
@$(call MKDIR,$(BUILD_DIR))
|
@$(call MKDIR,$(BUILD_DIR))
|
||||||
$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/agentctl$(EXE) ./cmd/agentctl
|
$(CGO_LOCALE) $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/agentctl$(EXE) ./cmd/agentctl
|
||||||
|
|
||||||
## Build only the agentctl helpers needed by the development profile.
|
## Build only the agentctl helpers needed by the development profile.
|
||||||
build-agentctl-dev: $(DEV_AGENTCTL_TARGETS)
|
build-agentctl-dev: $(DEV_AGENTCTL_TARGETS)
|
||||||
@@ -321,7 +346,7 @@ start-debug: build
|
|||||||
## Run tests
|
## Run tests
|
||||||
test:
|
test:
|
||||||
@echo "Running tests..."
|
@echo "Running tests..."
|
||||||
$(CGO_PREFIX) $(GO) test -tags fts5 ./...
|
$(CGO_LOCALE) $(CGO_PREFIX) $(GO) test -tags fts5 ./...
|
||||||
|
|
||||||
## Run only the Windows-sensitive packages — mirrors the test-windows job in
|
## Run only the Windows-sensitive packages — mirrors the test-windows job in
|
||||||
## .github/workflows/backend-tests.yml. The broader suite uses Unix-only
|
## .github/workflows/backend-tests.yml. The broader suite uses Unix-only
|
||||||
@@ -331,11 +356,11 @@ test:
|
|||||||
## runtime.GOOS guards in source.
|
## runtime.GOOS guards in source.
|
||||||
test-windows:
|
test-windows:
|
||||||
@echo "Running Windows-sensitive packages..."
|
@echo "Running Windows-sensitive packages..."
|
||||||
$(CGO_PREFIX) $(GO) test -tags fts5 -v ./internal/agentctl/server/process/... ./internal/agent/runtime/agentctl/launcher/... ./internal/backendapp/ownershiplock/...
|
$(CGO_LOCALE) $(CGO_PREFIX) $(GO) test -tags fts5 -v ./internal/agentctl/server/process/... ./internal/agent/runtime/agentctl/launcher/... ./internal/backendapp/ownershiplock/...
|
||||||
@echo "Running Windows port-collision tests..."
|
@echo "Running Windows port-collision tests..."
|
||||||
$(CGO_PREFIX) $(GO) test -tags fts5 -v ./internal/common/netutil -run TestIsAddrInUse
|
$(CGO_LOCALE) $(CGO_PREFIX) $(GO) test -tags fts5 -v ./internal/common/netutil -run TestIsAddrInUse
|
||||||
$(CGO_PREFIX) $(GO) test -tags fts5 -v ./internal/agentctl/server/instance -run TestAllocatePortAndListenerRetriesAddressInUse
|
$(CGO_LOCALE) $(CGO_PREFIX) $(GO) test -tags fts5 -v ./internal/agentctl/server/instance -run TestAllocatePortAndListenerRetriesAddressInUse
|
||||||
$(CGO_PREFIX) $(GO) test -tags fts5 -v ./internal/gateway/websocket -run TestResolveAndBindReportsOccupiedPort
|
$(CGO_LOCALE) $(CGO_PREFIX) $(GO) test -tags fts5 -v ./internal/gateway/websocket -run TestResolveAndBindReportsOccupiedPort
|
||||||
|
|
||||||
## Verify run/dev/start-debug expand to the right per-shell command line across
|
## Verify run/dev/start-debug expand to the right per-shell command line across
|
||||||
## native Windows (cmd), Git Bash/MSYS, and Unix — the Make-target dispatch the
|
## native Windows (cmd), Git Bash/MSYS, and Unix — the Make-target dispatch the
|
||||||
@@ -368,12 +393,12 @@ test-sprites-e2e: build-agentctl-linux
|
|||||||
LIFECYCLE_GOLEAK_COUNT ?= 20
|
LIFECYCLE_GOLEAK_COUNT ?= 20
|
||||||
test-lifecycle-goleak:
|
test-lifecycle-goleak:
|
||||||
@echo "Stress-running lifecycle goleak suite ($(LIFECYCLE_GOLEAK_COUNT)x)..."
|
@echo "Stress-running lifecycle goleak suite ($(LIFECYCLE_GOLEAK_COUNT)x)..."
|
||||||
$(CGO_PREFIX) $(GO) test -tags fts5 -race -count=$(LIFECYCLE_GOLEAK_COUNT) -timeout 600s ./internal/agent/runtime/lifecycle/ ./internal/agent/runtime/agentctl/
|
$(CGO_LOCALE) $(CGO_PREFIX) $(GO) test -tags fts5 -race -count=$(LIFECYCLE_GOLEAK_COUNT) -timeout 600s ./internal/agent/runtime/lifecycle/ ./internal/agent/runtime/agentctl/
|
||||||
|
|
||||||
## Run tests with coverage
|
## Run tests with coverage
|
||||||
test-coverage:
|
test-coverage:
|
||||||
@echo "Running tests with coverage..."
|
@echo "Running tests with coverage..."
|
||||||
$(CGO_PREFIX) $(GO) test -tags fts5 -v -coverprofile=coverage.out ./...
|
$(CGO_LOCALE) $(CGO_PREFIX) $(GO) test -tags fts5 -v -coverprofile=coverage.out ./...
|
||||||
$(GO) tool cover -html=coverage.out -o coverage.html
|
$(GO) tool cover -html=coverage.out -o coverage.html
|
||||||
|
|
||||||
## Regenerate Go stubs from proto/kandev/plugin/v1/plugin.proto (§3 of
|
## Regenerate Go stubs from proto/kandev/plugin/v1/plugin.proto (§3 of
|
||||||
@@ -406,7 +431,7 @@ lint:
|
|||||||
# supported target that could reach a finding before removing reported code.
|
# supported target that could reach a finding before removing reported code.
|
||||||
deadcode:
|
deadcode:
|
||||||
@echo "Scanning for unreachable Go functions (host configuration only; verify other targets before deleting findings; toolchain=$(DEADCODE_GO_TOOLCHAIN), override with DEADCODE_GO_TOOLCHAIN=go1.XY.Z)..."
|
@echo "Scanning for unreachable Go functions (host configuration only; verify other targets before deleting findings; toolchain=$(DEADCODE_GO_TOOLCHAIN), override with DEADCODE_GO_TOOLCHAIN=go1.XY.Z)..."
|
||||||
$(DEADCODE_ENV) $(GO) run golang.org/x/tools/cmd/deadcode@$(DEADCODE_VERSION) -tags fts5 ./...
|
$(CGO_LOCALE) $(DEADCODE_ENV) $(GO) run golang.org/x/tools/cmd/deadcode@$(DEADCODE_VERSION) -tags fts5 ./...
|
||||||
|
|
||||||
## Format code
|
## Format code
|
||||||
fmt:
|
fmt:
|
||||||
|
|||||||
@@ -202,6 +202,16 @@ winget install ezwinports.make
|
|||||||
|
|
||||||
The repository bootstrap script is Bash/package-manager oriented and does not provision a native Windows toolchain automatically. Use Git Bash or another compatible shell for Unix-oriented root recipes, install the pinned tools manually, and follow the contributor guide. `make dev` builds a `winjob.exe` helper so `Ctrl+C` can close a native development process tree.
|
The repository bootstrap script is Bash/package-manager oriented and does not provision a native Windows toolchain automatically. Use Git Bash or another compatible shell for Unix-oriented root recipes, install the pinned tools manually, and follow the contributor guide. `make dev` builds a `winjob.exe` helper so `Ctrl+C` can close a native development process tree.
|
||||||
|
|
||||||
|
### CGO link failure with a non-ASCII toolchain path
|
||||||
|
|
||||||
|
The `kandev` binary and the backend tests build with CGO and SQLite FTS5. If the MinGW/GCC toolchain resolves to a path with non-ASCII characters (most commonly an accented Windows username, which puts the toolchain under `C:\Users\<name>\`), a UTF-8 locale in Git Bash makes GCC mis-decode its own install path when it hands it to `ld`. The link then fails with `cannot find crt2.o`, `crtbegin.o`, and `-lm`, even though the files exist.
|
||||||
|
|
||||||
|
The CGO Make targets (`build-kandev`, `build-agentctl`, the `-tags fts5` test targets, and `deadcode`) set `LC_ALL=C` automatically when they run under Git Bash/MSYS on Windows. This prevents the CGO link error in `make build` and `make dev`. Use `make test-windows` for the supported Windows-clean test suite. The complete `make test-backend` suite still contains tests with Unix-only fixtures. `LC_ALL=C` (rather than `LANG=C`) is used so the fix still applies when the shell already exports `LC_ALL` or `LC_CTYPE` as a UTF-8 locale, which would otherwise override `LANG`. The prefix is empty on Unix and native `cmd`/PowerShell`; on Git Bash/MSYS it is always applied, which is a harmless no-op on an ASCII toolchain path and the actual fix on a non-ASCII one.
|
||||||
|
|
||||||
|
When invoking `go build`/`go test` directly, prefix the command with `LC_ALL=C` (for example `LC_ALL=C go build -tags fts5 ./cmd/kandev`). To remove the cause entirely, install the toolchain at an ASCII path outside `C:\Users\<name>` and point `CC`/`CXX` at it. Do not force the C locale globally (for example `export LC_ALL=C`) in your shell profile; that disables the UTF-8 locale for all of Git Bash.
|
||||||
|
|
||||||
|
A third option removes the cause at the OS level. Enable Windows' **Beta: Use Unicode UTF-8 for worldwide language support** (Region control panel, **Administrative** tab, **Change system locale**, then reboot). This sets the system ANSI code page to UTF-8, so GCC and `ld` agree on the path encoding and the accented toolchain path links directly under a UTF-8 locale, with no `LC_ALL=C` prefix and no toolchain relocation. It is a **beta**, system-wide setting: it requires a reboot and can change how other non-UTF-8 applications interpret text, so treat it as opt-in.
|
||||||
|
|
||||||
Backend Windows CI runs `go build ./...`, `go vet ./...`, and focused race tests for Windows-sensitive process, agent-launcher, instance-port, and websocket-tunnel packages. It does not run every backend package or the full product E2E suite. The repository's broader Windows-clean target adds web and CLI unit tests:
|
Backend Windows CI runs `go build ./...`, `go vet ./...`, and focused race tests for Windows-sensitive process, agent-launcher, instance-port, and websocket-tunnel packages. It does not run every backend package or the full product E2E suite. The repository's broader Windows-clean target adds web and CLI unit tests:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
|||||||
@@ -86,6 +86,11 @@ expect git-bash Windows_NT MINGW64 start-debug "KANDEV_DEBUG_PPROF_ENABLED=true
|
|||||||
|
|
||||||
# deadcode — the analyzer is invoked through the same shell-specific env-prefix
|
# deadcode — the analyzer is invoked through the same shell-specific env-prefix
|
||||||
# mechanism, but its command has no __backend marker, so match its module path.
|
# mechanism, but its command has no __backend marker, so match its module path.
|
||||||
|
# Git Bash additionally carries the CGO_LOCALE prefix (LC_ALL=C): deadcode compiles
|
||||||
|
# the -tags fts5 cgo packages, and this mingw gcc mis-decodes its own non-ASCII
|
||||||
|
# install path under a UTF-8 locale (see docs/public/windows-support.md). LC_ALL=C
|
||||||
|
# (not LANG=C) is used so it overrides an inherited LC_ALL/LC_CTYPE. Unix and
|
||||||
|
# native cmd stay locale-free.
|
||||||
expect unix '' '' deadcode \
|
expect unix '' '' deadcode \
|
||||||
'GOTOOLCHAIN=go1.26.0 go run golang.org/x/tools/cmd/deadcode@v0.48.0 -tags fts5 ./...' \
|
'GOTOOLCHAIN=go1.26.0 go run golang.org/x/tools/cmd/deadcode@v0.48.0 -tags fts5 ./...' \
|
||||||
'golang.org/x/tools/cmd/deadcode'
|
'golang.org/x/tools/cmd/deadcode'
|
||||||
@@ -93,9 +98,23 @@ expect windows-native Windows_NT '' deadcode \
|
|||||||
'set GOTOOLCHAIN=go1.26.0& go run golang.org/x/tools/cmd/deadcode@v0.48.0 -tags fts5 ./...' \
|
'set GOTOOLCHAIN=go1.26.0& go run golang.org/x/tools/cmd/deadcode@v0.48.0 -tags fts5 ./...' \
|
||||||
'golang.org/x/tools/cmd/deadcode'
|
'golang.org/x/tools/cmd/deadcode'
|
||||||
expect git-bash Windows_NT MINGW64 deadcode \
|
expect git-bash Windows_NT MINGW64 deadcode \
|
||||||
'GOTOOLCHAIN=go1.26.0 go run golang.org/x/tools/cmd/deadcode@v0.48.0 -tags fts5 ./...' \
|
'LC_ALL=C GOTOOLCHAIN=go1.26.0 go run golang.org/x/tools/cmd/deadcode@v0.48.0 -tags fts5 ./...' \
|
||||||
'golang.org/x/tools/cmd/deadcode'
|
'golang.org/x/tools/cmd/deadcode'
|
||||||
|
|
||||||
|
# test — a second CGO_LOCALE consumer, so the prefix is not proven for deadcode
|
||||||
|
# alone. The `test` recipe is deterministic (no version/timestamp ldflags), so
|
||||||
|
# assert it in full across all three shells: Git Bash carries LC_ALL=C, Unix and
|
||||||
|
# native cmd do not.
|
||||||
|
expect git-bash Windows_NT MINGW64 test \
|
||||||
|
'LC_ALL=C CGO_ENABLED=1 go test -tags fts5 ./...' \
|
||||||
|
'go test -tags fts5'
|
||||||
|
expect unix '' '' test \
|
||||||
|
'CGO_ENABLED=1 go test -tags fts5 ./...' \
|
||||||
|
'go test -tags fts5'
|
||||||
|
expect windows-native Windows_NT '' test \
|
||||||
|
'set CGO_ENABLED=1& go test -tags fts5 ./...' \
|
||||||
|
'go test -tags fts5'
|
||||||
|
|
||||||
# mkdir — the Windows branch must check for the directory before calling cmd's
|
# mkdir — the Windows branch must check for the directory before calling cmd's
|
||||||
# mkdir, so rebuilding over an existing bin/ stays quiet while other errors stay
|
# mkdir, so rebuilding over an existing bin/ stays quiet while other errors stay
|
||||||
# visible. Unix and Git Bash use idempotent `mkdir -p`, which is silent by design.
|
# visible. Unix and Git Bash use idempotent `mkdir -p`, which is silent by design.
|
||||||
@@ -176,7 +195,30 @@ else
|
|||||||
printf 'ok %-16s %s\n' "static" 'no literal /dev/null inside $(shell ...)'
|
printf 'ok %-16s %s\n' "static" 'no literal /dev/null inside $(shell ...)'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Static guard: native cgo host builds carry the $(CGO_LOCALE) prefix.
|
||||||
|
#
|
||||||
|
# kandev (-tags fts5) and agentctl both import cgo (go list -deps reports CgoFiles),
|
||||||
|
# so their native `go build` invokes gcc and fails on a non-ASCII toolchain path
|
||||||
|
# under Git Bash unless prefixed with $(CGO_LOCALE) — exactly the miss that first
|
||||||
|
# shipped build-agentctl without it. Cross builds set CGO_ENABLED=0 / GOOS= and do
|
||||||
|
# not invoke gcc, so they are excluded. Extend the cmd list here if another host
|
||||||
|
# binary starts importing cgo.
|
||||||
|
cgo_status=0
|
||||||
|
cgo_unprefixed=$(grep -nE '\$\(GO\) build' "$backend_dir/Makefile" \
|
||||||
|
| grep -E '\./cmd/(kandev|agentctl)$' \
|
||||||
|
| grep -vE 'CGO_LOCALE|GOOS=|\$\(GO_(LINUX|DARWIN)') || cgo_status=$?
|
||||||
|
if [ "$cgo_status" -gt 1 ]; then
|
||||||
|
printf 'FAIL %-16s unable to scan Makefile (grep exit %s)\n' "static-cgo" "$cgo_status"
|
||||||
|
status=1
|
||||||
|
elif [ -n "$cgo_unprefixed" ]; then
|
||||||
|
printf 'FAIL %-16s native cgo build without $(CGO_LOCALE):\n' "static-cgo"
|
||||||
|
printf ' %s\n' "$cgo_unprefixed"
|
||||||
|
status=1
|
||||||
|
else
|
||||||
|
printf 'ok %-16s %s\n' "static-cgo" 'native cgo host builds carry $(CGO_LOCALE)'
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$status" -eq 0 ]; then
|
if [ "$status" -eq 0 ]; then
|
||||||
echo "All Makefile shell-dispatch checks passed (run/dev/start-debug/deadcode/build-kandev across 3 shells, plus static guard)."
|
echo "All Makefile shell-dispatch checks passed (run/dev/start-debug/deadcode/test/build-kandev across 3 shells, plus static guards)."
|
||||||
fi
|
fi
|
||||||
exit "$status"
|
exit "$status"
|
||||||
|
|||||||
Reference in New Issue
Block a user