fix(build): silence Windows mkdir "already exists" noise in build logs (#2753)

Co-authored-by: Joao Manso <X363081@bcpcorp.net>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
This commit is contained in:
João Manso
2026-08-18 06:55:54 +00:00
committed by GitHub
parent ba96562f04
commit a4c346c419
2 changed files with 82 additions and 22 deletions
+24 -19
View File
@@ -45,10 +45,13 @@ 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`.
# Call sites use `-@$(MKDIR) $(BUILD_DIR)` (note the `-` prefix) so the
# "already exists" failure is tolerated; on Unix the prefix is a no-op.
MKDIR = cmd /c mkdir
# cmd's `mkdir` errors and prints "A subdirectory or file <dir> already
# exists." when the dir is already there, unlike Unix `mkdir -p`. Check for
# the directory first so that expected repeats stay quiet while permission
# and invalid-path errors keep their stderr and exit status.
define MKDIR
cmd /c if not exist "$(subst /,\,$(1))" mkdir "$(subst /,\,$(1))"
endef
CGO_PREFIX = set CGO_ENABLED=1&
DEADCODE_ENV = set GOTOOLCHAIN=$(DEADCODE_GO_TOOLCHAIN)&
# Cross-compile prefixes for agentctl helpers bundled with the host build.
@@ -78,7 +81,9 @@ ifeq ($(OS)$(MSYSTEM),Windows_NT)
else
RM = rm -f
RMDIR = rm -rf
MKDIR = mkdir -p
define MKDIR
mkdir -p "$(1)"
endef
CGO_PREFIX = CGO_ENABLED=1
DEADCODE_ENV = GOTOOLCHAIN=$(DEADCODE_GO_TOOLCHAIN)
GO_LINUX_AMD64 = CGO_ENABLED=0 GOOS=linux GOARCH=amd64
@@ -166,13 +171,13 @@ all: build
## Build the preview CLI for deploying PR preview environments to Sprites
build-preview:
@echo "Building preview..."
-@$(MKDIR) $(BUILD_DIR)
@$(call MKDIR,$(BUILD_DIR))
$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/preview$(EXE) ./cmd/preview
## Build the mock-agent binary for testing
build-mock-agent:
@echo "Building mock-agent..."
-@$(MKDIR) $(BUILD_DIR)
@$(call MKDIR,$(BUILD_DIR))
$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/mock-agent$(EXE) ./cmd/mock-agent
## Build the winjob helper — wraps a subprocess in a Windows Job Object so
@@ -183,7 +188,7 @@ build-mock-agent:
## helper stays for any other consumer.
build-winjob:
@echo "Building winjob..."
-@$(MKDIR) $(BUILD_DIR)
@$(call MKDIR,$(BUILD_DIR))
$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/winjob$(EXE) ./cmd/winjob
## Build and run acpdbg — usage: make acpdbg ARGS="probe auggie"
@@ -193,7 +198,7 @@ acpdbg: build-acpdbg
## Build the acpdbg binary — raw ACP JSON-RPC debugger (see .agents/skills/acp-debug)
build-acpdbg:
@echo "Building acpdbg..."
-@$(MKDIR) $(BUILD_DIR)
@$(call MKDIR,$(BUILD_DIR))
$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/acpdbg$(EXE) ./cmd/acpdbg
## Build the complete development binary set.
@@ -214,7 +219,7 @@ build-runtime: build-agentctl build-agentctl-remote build-kandev
## launcher prebuild does not pull in agentctl helpers it does not use yet.
build-kandev:
@echo "Building $(BINARY_NAME)..."
-@$(MKDIR) $(BUILD_DIR)
@$(call MKDIR,$(BUILD_DIR))
$(CGO_PREFIX) $(GO) build $(GOFLAGS) -tags fts5 -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)$(EXE) ./cmd/kandev
## Build all binaries (unified + individual services + agentctl)
@@ -225,7 +230,7 @@ build-all: build build-agentctl
## Build the agentctl sidecar binary (includes embedded MCP server)
build-agentctl:
@echo "Building agentctl..."
-@$(MKDIR) $(BUILD_DIR)
@$(call MKDIR,$(BUILD_DIR))
$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/agentctl$(EXE) ./cmd/agentctl
## Build only the agentctl helpers needed by the development profile.
@@ -237,33 +242,33 @@ build-agentctl-remote: build-agentctl-linux build-agentctl-linux-arm64 build-age
## Build agentctl for linux/amd64 (used by Docker/Sprites and Linux SSH hosts)
build-agentctl-linux:
@echo "Building agentctl for linux/amd64..."
-@$(MKDIR) $(BUILD_DIR)
@$(call MKDIR,$(BUILD_DIR))
$(GO_LINUX_AMD64) $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/agentctl-linux-amd64 ./cmd/agentctl
## Build agentctl for linux/arm64 (used by ARM Linux SSH hosts, e.g. Graviton)
build-agentctl-linux-arm64:
@echo "Building agentctl for linux/arm64..."
-@$(MKDIR) $(BUILD_DIR)
@$(call MKDIR,$(BUILD_DIR))
$(GO_LINUX_ARM64) $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/agentctl-linux-arm64 ./cmd/agentctl
## Build agentctl for darwin/arm64 (used by Apple Silicon SSH hosts)
build-agentctl-darwin-arm64:
@echo "Building agentctl for darwin/arm64..."
-@$(MKDIR) $(BUILD_DIR)
@$(call MKDIR,$(BUILD_DIR))
$(GO_DARWIN_ARM64) $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/agentctl-darwin-arm64 ./cmd/agentctl
$(call adhoc_sign_darwin,$(BUILD_DIR)/agentctl-darwin-arm64)
## Build agentctl for darwin/amd64 (used by Intel Mac SSH hosts)
build-agentctl-darwin-amd64:
@echo "Building agentctl for darwin/amd64..."
-@$(MKDIR) $(BUILD_DIR)
@$(call MKDIR,$(BUILD_DIR))
$(GO_DARWIN_AMD64) $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/agentctl-darwin-amd64 ./cmd/agentctl
$(call adhoc_sign_darwin,$(BUILD_DIR)/agentctl-darwin-amd64)
## Build mock-agent for linux/amd64 (used by Docker E2E tests)
build-mock-agent-linux:
@echo "Building mock-agent for linux/amd64..."
@$(MKDIR) $(BUILD_DIR)
@$(call MKDIR,$(BUILD_DIR))
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/mock-agent-linux-amd64 ./cmd/mock-agent
@@ -284,7 +289,7 @@ HOST_GOARCH := $(shell $(GO) env GOARCH)
e2e-plugin-package:
@echo "Packaging plugin-fixture for e2e ($(HOST_GOOS)-$(HOST_GOARCH))..."
@$(RMDIR) $(FIXTURE_STAGE_DIR)
@$(MKDIR) $(FIXTURE_STAGE_DIR)/server
@$(call MKDIR,$(FIXTURE_STAGE_DIR)/server)
cp $(FIXTURE_PACKAGE_DIR)/manifest.yaml $(FIXTURE_STAGE_DIR)/manifest.yaml
cp -r $(FIXTURE_PACKAGE_DIR)/ui $(FIXTURE_STAGE_DIR)/ui
$(GO) build $(GOFLAGS) -o $(FIXTURE_STAGE_DIR)/server/plugin-$(HOST_GOOS)-$(HOST_GOARCH)$(EXE) ./cmd/plugin-fixture
@@ -385,7 +390,7 @@ PROTOC_GEN_GO_GRPC_VERSION := v1.5.1
.PHONY: proto
proto:
@echo "Generating proto stubs..."
@$(MKDIR) $(PROTO_TOOLS_BIN)
@$(call MKDIR,$(PROTO_TOOLS_BIN))
GOBIN=$(PROTO_TOOLS_BIN) $(GO) install google.golang.org/protobuf/cmd/protoc-gen-go@$(PROTOC_GEN_GO_VERSION)
GOBIN=$(PROTO_TOOLS_BIN) $(GO) install google.golang.org/grpc/cmd/protoc-gen-go-grpc@$(PROTOC_GEN_GO_GRPC_VERSION)
cd proto && PATH="$(PROTO_TOOLS_BIN):$$PATH" $(GO) run github.com/bufbuild/buf/cmd/buf@$(BUF_VERSION) generate
@@ -426,7 +431,7 @@ deps:
## Build for multiple platforms
build-cross:
@echo "Building for multiple platforms..."
-@$(MKDIR) $(BUILD_DIR)
@$(call MKDIR,$(BUILD_DIR))
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 $(GO) build -tags fts5 -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 ./cmd/kandev
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 $(GO) build -tags fts5 -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-amd64 ./cmd/kandev
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 $(GO) build -tags fts5 -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64 ./cmd/kandev
+58 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# check-make-shells — assert that apps/backend/Makefile's run/dev/start-debug
# and deadcode targets expand to the correct per-shell command line, without
# running them.
# building them.
#
# The Makefile dispatches on two independent axes:
# * EXE suffix — keyed on $(OS): any Windows build (native cmd/PowerShell
@@ -19,13 +19,15 @@
# 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.
# that the backend Go CI jobs don't exercise. When cmd.exe is available, the
# script also runs an isolated mkdir probe without building any binaries.
set -euo pipefail
backend_dir="$(cd "$(dirname "$0")/../apps/backend" && pwd)"
status=0
windows_mkdir_recipe=
# expect <label> <OS> <MSYSTEM> <target> <expected recipe tail>
expect() {
@@ -51,6 +53,9 @@ expect() {
fi
if [ "$got" = "$want" ]; then
printf 'ok %-16s %-12s %s\n' "$label" "$target" "$got"
if [ "$label" = windows-native ] && [ "$target" = build-kandev ]; then
windows_mkdir_recipe="$got"
fi
else
printf 'FAIL %-16s %-12s\n want: %s\n got: %s\n' \
"$label" "$target" "$want" "$got"
@@ -91,6 +96,56 @@ expect git-bash Windows_NT MINGW64 deadcode \
'GOTOOLCHAIN=go1.26.0 go run golang.org/x/tools/cmd/deadcode@v0.48.0 -tags fts5 ./...' \
'golang.org/x/tools/cmd/deadcode'
# 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
# visible. Unix and Git Bash use idempotent `mkdir -p`, which is silent by design.
expect unix '' '' build-kandev 'mkdir -p "bin"' mkdir
expect windows-native Windows_NT '' build-kandev 'cmd /c if not exist "bin" mkdir "bin"' mkdir
expect git-bash Windows_NT MINGW64 build-kandev 'mkdir -p "bin"' mkdir
# When cmd.exe is available, execute the captured native-Windows recipe against
# a temporary directory. This checks that an existing directory stays quiet.
check_native_mkdir_behavior() {
local recipe=$1 cmd_bin inner probe_name probe_dir stderr_file first_status
if ! cmd_bin=$(command -v cmd.exe 2>/dev/null); then
printf 'skip %-16s cmd.exe is not available\n' "native-mkdir"
return
fi
probe_name=".make-shell-check-$$"
probe_dir="$backend_dir/$probe_name"
stderr_file=$(mktemp)
inner=${recipe#cmd /c }
inner=${inner//bin/$probe_name}
rm -rf "$probe_dir"
first_status=0
(cd "$backend_dir" && "$cmd_bin" /c "$inner") 2>"$stderr_file" || first_status=$?
if [ "$first_status" -ne 0 ] || [ ! -d "$probe_dir" ] || [ -s "$stderr_file" ]; then
printf 'FAIL %-16s first mkdir did not create a quiet directory\n' "native-mkdir"
status=1
rm -rf "$probe_dir"
rm -f "$stderr_file"
return
fi
: >"$stderr_file"
(cd "$backend_dir" && "$cmd_bin" /c "$inner") 2>"$stderr_file" || true
if [ -s "$stderr_file" ]; then
printf 'FAIL %-16s existing directory wrote to stderr\n' "native-mkdir"
status=1
else
printf 'ok %-16s existing directory stayed quiet\n' "native-mkdir"
fi
rm -rf "$probe_dir"
rm -f "$stderr_file"
}
if [ -n "$windows_mkdir_recipe" ]; then
check_native_mkdir_behavior "$windows_mkdir_recipe"
fi
# Static guard: no literal /dev/null inside $(shell ...).
#
# Unlike a recipe, which only ever runs on a platform its target supports,
@@ -122,6 +177,6 @@ else
fi
if [ "$status" -eq 0 ]; then
echo "All Makefile shell-dispatch checks passed (3 shells x 4 targets, plus static guard)."
echo "All Makefile shell-dispatch checks passed (run/dev/start-debug/deadcode/build-kandev across 3 shells, plus static guard)."
fi
exit "$status"