fix(build): support make dev on native Windows (#1886)

This commit is contained in:
João Manso
2026-07-24 12:08:22 +01:00
committed by GitHub
parent 34946011a1
commit 6fedac6c64
4 changed files with 128 additions and 14 deletions
+9
View File
@@ -5,6 +5,7 @@ on:
branches: [main]
paths:
- "apps/backend/**"
- "scripts/check-make-shells"
- ".github/workflows/backend-tests.yml"
- "!**/*.md"
- "!**/*.mdx"
@@ -13,6 +14,7 @@ on:
branches: [main]
paths:
- "apps/backend/**"
- "scripts/check-make-shells"
- ".github/workflows/backend-tests.yml"
- "!**/*.md"
- "!**/*.mdx"
@@ -80,6 +82,13 @@ jobs:
- name: Build
run: go build -v ./cmd/kandev
# Assert the run/dev/start-debug targets expand to the right per-shell
# command line (native Windows / Git Bash / Unix). The windows-latest job
# below compiles Go but never exercises this Make-target dispatch, so a
# regression in the OS/MSYSTEM guards would otherwise ship unnoticed.
- name: Check Makefile shell dispatch
run: make check-make-shells
- name: Resolve base SHA
shell: bash
working-directory: ${{ github.workspace }}
+8
View File
@@ -150,7 +150,15 @@ bootstrap-e2e:
.PHONY: doctor
doctor:
# Native Windows (cmd/PowerShell) has no POSIX shell for the bash-only doctor
# script. Under Git Bash/MSYS ($(MSYSTEM) is set) it runs fine, so skip only
# when OS is Windows_NT AND MSYSTEM is empty — the concatenation equals
# "Windows_NT" only in that native case.
ifeq ($(OS)$(MSYSTEM),Windows_NT)
@echo pre-commit hooks skipped on native Windows - run scripts/doctor from Git Bash to enable
else
@scripts/doctor
endif
.PHONY: dev
dev: doctor
+48 -14
View File
@@ -9,17 +9,30 @@ LDFLAGS := -s -w
# Cross-platform commands
#
# On Windows, GNU Make invokes commands through cmd.exe, which does not
# support Unix `NAME=VAL cmd` env-var-prefix syntax. Instead, `set NAME=VAL&`
# (single ampersand) chains a subsequent command in the same shell, scoping
# the env var to one recipe line — the closest cmd analog to the Unix form.
# `EXE` adds the .exe suffix to build outputs; Go does NOT auto-append it
# when `-o` is explicit.
# Two independent axes decide these variables; conflating them breaks Git
# Bash/MSYS, which sets OS=Windows_NT yet runs recipes through sh:
#
# 1. Executable suffix (EXE) — keyed on $(OS). Go emits a `.exe` for
# GOOS=windows regardless of the calling shell, so native cmd/PowerShell
# AND Git Bash both need it. Go does NOT auto-append it when `-o` is set.
# 2. Shell syntax — keyed on $(OS)$(MSYSTEM). Only native cmd/PowerShell needs
# `set NAME=VAL&` (single ampersand chains a command in the same shell,
# scoping the env var to one recipe line — the closest cmd analog to the
# Unix `NAME=VAL cmd` prefix), backslashed paths, and no `exec`. Under Git
# Bash/MSYS $(MSYSTEM) is set (e.g. MINGW64), so the concatenation differs
# from "Windows_NT" and recipes take the POSIX branch — matching the shell
# they run in. Mirrors the doctor guard in the root Makefile.
#
# Install GNU Make on Windows with `winget install ezwinports.make` (or via
# scoop/chocolatey). Targets that rely on Unix builtins (run, dev,
# start-debug, build-cross) remain Unix-only.
# scoop/chocolatey). build-cross remains Unix-only — it cross-compiles with
# CGO for other OSes, which needs Unix toolchains.
ifeq ($(OS),Windows_NT)
EXE := .exe
else
EXE :=
endif
ifeq ($(OS)$(MSYSTEM),Windows_NT)
RM = cmd /c del /s /q
RMDIR = cmd /c rmdir /s /q
# cmd's `mkdir` errors when the dir already exists, unlike Unix `mkdir -p`.
@@ -34,7 +47,16 @@ ifeq ($(OS),Windows_NT)
GO_LINUX_ARM64 = set CGO_ENABLED=0& set GOOS=linux& set GOARCH=arm64&
GO_DARWIN_ARM64 = set CGO_ENABLED=0& set GOOS=darwin& set GOARCH=arm64&
GO_DARWIN_AMD64 = set CGO_ENABLED=0& set GOOS=darwin& set GOARCH=amd64&
EXE = .exe
# Run-target env prefixes and binary invocation. cmd.exe has no `NAME=VAL cmd`
# syntax (use `set NAME=VAL&`) and no `exec`; the binary path needs backslashes.
DEV_ENV = set KANDEV_DEBUG_PPROF_ENABLED=true&
DEBUG_ENV = set KANDEV_DEBUG_PPROF_ENABLED=true& set KANDEV_LOG_LEVEL=debug&
EXEC =
RUN_BIN = "$(subst /,\,$(BUILD_DIR)/$(BINARY_NAME)$(EXE))"
# check-make-shells is a bash script; native cmd.exe can't run its shebang, so
# print a skip note instead (mirrors the root Makefile's doctor guard). CI runs
# the real check on Linux, and Git Bash/WSL run it locally.
CHECK_SHELLS = @echo check-make-shells is bash-only - run it from Git Bash/WSL or the Linux CI
# Windows recipes run under cmd.exe, which has no POSIX `if command -v ...; then`
# syntax. Neither codesign nor rcodesign ships on Windows anyway, so ad-hoc
# signing is a no-op here: darwin/arm64 stays Go-signed at link time, and
@@ -51,7 +73,11 @@ else
GO_LINUX_ARM64 = CGO_ENABLED=0 GOOS=linux GOARCH=arm64
GO_DARWIN_ARM64 = CGO_ENABLED=0 GOOS=darwin GOARCH=arm64
GO_DARWIN_AMD64 = CGO_ENABLED=0 GOOS=darwin GOARCH=amd64
EXE =
DEV_ENV = KANDEV_DEBUG_PPROF_ENABLED=true
DEBUG_ENV = KANDEV_DEBUG_PPROF_ENABLED=true KANDEV_LOG_LEVEL=debug
EXEC = exec
RUN_BIN = ./$(BUILD_DIR)/$(BINARY_NAME)$(EXE)
CHECK_SHELLS = @../../scripts/check-make-shells
# Ad-hoc sign a darwin Mach-O so Apple Silicon (AMFI) will run it; ad-hoc needs
# no Apple identity. Go already ad-hoc-signs darwin/arm64 at link time, so this
# is mainly to (a) sign darwin/amd64 too and (b) re-sign uniformly. Prefer
@@ -75,7 +101,7 @@ BUILD_TIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || powershell -c
LDFLAGS += -X main.Version=$(VERSION) -X main.Commit=$(COMMIT) -X main.BuildTime=$(BUILD_TIME)
.PHONY: all build build-all build-agentctl build-agentctl-remote build-agentctl-linux build-agentctl-linux-arm64 build-agentctl-darwin-arm64 build-agentctl-darwin-amd64 build-acpdbg acpdbg build-mock-agent build-mock-agent-linux build-preview build-winjob e2e-plugin-package clean run dev start-debug test test-e2e test-sprites-e2e test-lifecycle-goleak lint fmt vet help
.PHONY: all build build-all build-agentctl build-agentctl-remote build-agentctl-linux build-agentctl-linux-arm64 build-agentctl-darwin-arm64 build-agentctl-darwin-amd64 build-acpdbg acpdbg build-mock-agent build-mock-agent-linux build-preview build-winjob e2e-plugin-package clean run dev start-debug test test-e2e test-sprites-e2e test-lifecycle-goleak lint fmt vet check-make-shells help
## Default target
all: build
@@ -200,17 +226,17 @@ clean:
## Run the application
run: build
@echo "Running $(BINARY_NAME)..."
./$(BUILD_DIR)/$(BINARY_NAME)$(EXE) __backend
$(RUN_BIN) __backend
## Run in development mode (with pprof enabled)
dev: build
@echo "Running $(BINARY_NAME) in development mode..."
@KANDEV_DEBUG_PPROF_ENABLED=true exec ./$(BUILD_DIR)/$(BINARY_NAME)$(EXE) __backend
@$(DEV_ENV) $(EXEC) $(RUN_BIN) __backend
## Run in debug mode (pprof + debug logging)
start-debug: build
@echo "Running $(BINARY_NAME) in debug mode (pprof + debug logging)..."
@KANDEV_DEBUG_PPROF_ENABLED=true KANDEV_LOG_LEVEL=debug exec ./$(BUILD_DIR)/$(BINARY_NAME)$(EXE) __backend
@$(DEBUG_ENV) $(EXEC) $(RUN_BIN) __backend
## Run tests
test:
@@ -227,6 +253,13 @@ test-windows:
@echo "Running Windows-sensitive packages..."
$(CGO_PREFIX) $(GO) test -tags fts5 -v ./internal/agentctl/server/process/... ./internal/agent/runtime/agentctl/launcher/...
## 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
## Go CI jobs don't exercise. The harness is a bash script, so it runs under Git
## Bash/WSL or the Linux CI; on native cmd/PowerShell it prints a skip note.
check-make-shells:
$(CHECK_SHELLS)
## Run E2E tests (real agents, costs money — manual only)
## Set agent commands via env vars: E2E_CLAUDE_CODE_COMMAND, E2E_AMP_COMMAND
## Enable debug: KANDEV_DEBUG_AGENT_MESSAGES=true KANDEV_DEBUG_LOG_DIR=/tmp/e2e-debug
@@ -331,6 +364,7 @@ help:
@echo " start-debug Run in debug mode (pprof + debug logging)"
@echo " test Run tests (full suite — fails on Windows pending fixture cleanup)"
@echo " test-windows Run only the Windows-clean subset (matches CI windows-latest job)"
@echo " check-make-shells Assert run/dev/start-debug expand correctly per shell (make -n)"
@echo " test-e2e Run E2E adapter tests (real agents, costs money)"
@echo " e2e-plugin-package Package the plugin-fixture SDK plugin into .build/kandev-plugin-e2e-1.0.0.tar.gz"
@echo " test-lifecycle-goleak Stress-run lifecycle goleak suite (override LIFECYCLE_GOLEAK_COUNT=N)"
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# check-make-shells — assert that apps/backend/Makefile's run/dev/start-debug
# targets expand to the correct per-shell command line, without running them.
#
# The Makefile dispatches on two independent axes:
# * EXE suffix — keyed on $(OS): any Windows build (native cmd/PowerShell
# OR Git Bash) produces kandev.exe.
# * shell syntax — keyed on $(OS)$(MSYSTEM): only native cmd/PowerShell uses
# `set VAR=VAL&` + backslash path + no exec; Unix and Git
# Bash/MSYS use POSIX `VAR=val exec`.
# Git Bash is the trap — it sets OS=Windows_NT yet runs recipes through sh, so
# it must take the POSIX branch while keeping the .exe suffix. Conflating the
# two axes (the pre-fix bug) made Git Bash emit `set VAR=VAL&` into bash, where
# the env var is never exported.
#
# Each shell is simulated deterministically with make's OS=/MSYSTEM=
# command-line overrides, so one host checks the dispatch for all three shells
# without having to run on each. The harness itself is bash (grep, sed), so it
# needs a POSIX shell — it runs in the Linux CI job and under Git Bash/WSL
# locally, not from native cmd.exe/PowerShell. Covers the Make-target dispatch
# that the backend Go CI jobs don't exercise.
set -euo pipefail
backend_dir="$(cd "$(dirname "$0")/../apps/backend" && pwd)"
status=0
# expect <label> <OS> <MSYSTEM> <target> <expected recipe tail>
expect() {
local label=$1 os=$2 msys=$3 target=$4 want=$5 got
got=$(make -C "$backend_dir" OS="$os" MSYSTEM="$msys" -n "$target" 2>/dev/null \
| grep -F __backend \
| sed 's/^[[:space:]]*//; s/[[:space:]]*$//; s/[[:space:]]\{1,\}/ /g' \
|| true)
if [ "$got" = "$want" ]; then
printf 'ok %-16s %-12s %s\n' "$label" "$target" "$got"
else
printf 'FAIL %-16s %-12s\n want: %s\n got: %s\n' \
"$label" "$target" "$want" "$got"
status=1
fi
}
# Unix — no .exe, POSIX env-prefix + exec.
expect unix '' '' run './bin/kandev __backend'
expect unix '' '' dev 'KANDEV_DEBUG_PPROF_ENABLED=true exec ./bin/kandev __backend'
expect unix '' '' start-debug 'KANDEV_DEBUG_PPROF_ENABLED=true KANDEV_LOG_LEVEL=debug exec ./bin/kandev __backend'
# Native Windows (cmd/PowerShell) — .exe, `set VAR=VAL&`, backslash path, no exec.
expect windows-native Windows_NT '' run '"bin\kandev.exe" __backend'
expect windows-native Windows_NT '' dev 'set KANDEV_DEBUG_PPROF_ENABLED=true& "bin\kandev.exe" __backend'
expect windows-native Windows_NT '' start-debug 'set KANDEV_DEBUG_PPROF_ENABLED=true& set KANDEV_LOG_LEVEL=debug& "bin\kandev.exe" __backend'
# Git Bash/MSYS — .exe suffix but POSIX syntax (recipes run through sh).
expect git-bash Windows_NT MINGW64 run './bin/kandev.exe __backend'
expect git-bash Windows_NT MINGW64 dev 'KANDEV_DEBUG_PPROF_ENABLED=true exec ./bin/kandev.exe __backend'
expect git-bash Windows_NT MINGW64 start-debug 'KANDEV_DEBUG_PPROF_ENABLED=true KANDEV_LOG_LEVEL=debug exec ./bin/kandev.exe __backend'
if [ "$status" -eq 0 ]; then
echo "All Makefile shell-dispatch checks passed (3 shells x 3 targets)."
fi
exit "$status"