fix(build): make Makefile $(shell) probes work under native Windows cmd (#1953)
This commit is contained in:
@@ -5,6 +5,9 @@ on:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "apps/backend/**"
|
||||
# check-make-shells also lints the root Makefile's $(shell ...) probes, so
|
||||
# a change there has to be able to trip it.
|
||||
- "Makefile"
|
||||
- "scripts/check-make-shells"
|
||||
- ".github/workflows/backend-tests.yml"
|
||||
- "!**/*.md"
|
||||
@@ -14,6 +17,7 @@ on:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "apps/backend/**"
|
||||
- "Makefile"
|
||||
- "scripts/check-make-shells"
|
||||
- ".github/workflows/backend-tests.yml"
|
||||
- "!**/*.md"
|
||||
|
||||
@@ -22,6 +22,18 @@ else
|
||||
RMDIR = rm -rf
|
||||
endif
|
||||
|
||||
# stderr redirect for $(shell ...) probes. Keyed on $(OS)$(MSYSTEM) rather than
|
||||
# $(OS) alone: Git Bash sets OS=Windows_NT yet runs commands through sh, so it
|
||||
# needs the POSIX path. Under native cmd there is no /dev/null, and cmd resolves
|
||||
# redirections before running the command — `2>/dev/null` would abort the probe
|
||||
# and silently force its fallback. Holds the whole token so callers can blank it.
|
||||
# Mirrors apps/backend/Makefile.
|
||||
ifeq ($(OS)$(MSYSTEM),Windows_NT)
|
||||
NULL_REDIR := 2>NUL
|
||||
else
|
||||
NULL_REDIR := 2>/dev/null
|
||||
endif
|
||||
|
||||
# Colors for terminal output
|
||||
RESET := \033[0m
|
||||
BOLD := \033[1m
|
||||
@@ -33,10 +45,10 @@ YELLOW := \033[33m
|
||||
MAGENTA := \033[35m
|
||||
|
||||
VERBOSE ?= 0
|
||||
NODE ?= $(shell command -v node 2>/dev/null || echo node)
|
||||
NODE ?= $(shell command -v node $(NULL_REDIR) || echo node)
|
||||
SERVICE_LAUNCHER := $(CURDIR)/dist/kandev/bin/kandev
|
||||
SERVICE_BUNDLE_DIR := $(CURDIR)/dist/kandev
|
||||
SERVICE_VERSION := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev)
|
||||
SERVICE_VERSION := $(shell git rev-parse --short HEAD $(NULL_REDIR) || echo dev)
|
||||
SERVICE_ENV := KANDEV_BUNDLE_DIR="$(SERVICE_BUNDLE_DIR)" KANDEV_VERSION="$(SERVICE_VERSION)"
|
||||
SERVICE_PORT_FLAG := $(if $(PORT),--port $(PORT),)
|
||||
SERVICE_HOME_DIR_FLAG := $(if $(HOME_DIR),--home-dir "$(HOME_DIR)",)
|
||||
|
||||
+28
-3
@@ -95,9 +95,34 @@ else
|
||||
endif
|
||||
|
||||
# Build info
|
||||
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
||||
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
BUILD_TIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || powershell -c "Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ'" 2>/dev/null || echo "unknown")
|
||||
#
|
||||
# Keyed on $(OS)$(MSYSTEM) like the shell-syntax block above. cmd.exe has no
|
||||
# /dev/null and resolves redirections *before* running the command, so under
|
||||
# native cmd/PowerShell `2>/dev/null` aborts each command outright and hands
|
||||
# every value to its fallback — stamping local Windows builds dev/unknown/unknown
|
||||
# while printing "The system cannot find the path specified." per assignment.
|
||||
# cmd's own `date` is no substitute: given arguments it prompts for input and
|
||||
# still exits 0, so it would swallow the `||`. Git Bash sets MSYSTEM and runs
|
||||
# recipes through sh, so it takes the POSIX branch.
|
||||
#
|
||||
# Fallbacks are unquoted: sh strips the quotes, cmd's `echo` would keep them.
|
||||
#
|
||||
# NULL_REDIR holds the whole redirect token, not just the device path, so a
|
||||
# caller simulating another platform can blank it (`make NULL_REDIR=`) instead
|
||||
# of naming a device this shell would misread — see scripts/check-make-shells.
|
||||
ifeq ($(OS)$(MSYSTEM),Windows_NT)
|
||||
NULL_REDIR := 2>NUL
|
||||
# `Get-Date -Format` renders *local* time with a literal Z; UtcNow is real UTC.
|
||||
# InvariantCulture is not optional: ToString uses the current culture's
|
||||
# calendar, so a th-TH host stamps year 2569 and ar-SA stamps 1448.
|
||||
BUILD_TIME := $(shell powershell -NoProfile -Command "[DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ',[Globalization.CultureInfo]::InvariantCulture)" $(NULL_REDIR) || echo unknown)
|
||||
else
|
||||
NULL_REDIR := 2>/dev/null
|
||||
BUILD_TIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ' $(NULL_REDIR) || echo unknown)
|
||||
endif
|
||||
|
||||
VERSION ?= $(shell git describe --tags --always --dirty $(NULL_REDIR) || echo dev)
|
||||
COMMIT := $(shell git rev-parse --short HEAD $(NULL_REDIR) || echo unknown)
|
||||
|
||||
LDFLAGS += -X main.Version=$(VERSION) -X main.Commit=$(COMMIT) -X main.BuildTime=$(BUILD_TIME)
|
||||
|
||||
|
||||
@@ -29,7 +29,15 @@ 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 \
|
||||
# The OS=/MSYSTEM= overrides only steer which branch the Makefile picks;
|
||||
# $(shell ...) still runs under this harness's POSIX shell, and it expands
|
||||
# even under `make -n`. So neutralise the build-stamp probes rather than let
|
||||
# the windows-native case run them: NULL_REDIR=NUL would have sh create a
|
||||
# junk ./NUL, and BUILD_TIME would spawn PowerShell per case. Blanking
|
||||
# NULL_REDIR (rather than passing /dev/null) also dodges Git Bash rewriting
|
||||
# a /dev/null argument to `nul` on its way to native make. None of this
|
||||
# affects the run/dev/start-debug recipes under assertion.
|
||||
got=$(make -C "$backend_dir" OS="$os" MSYSTEM="$msys" NULL_REDIR= BUILD_TIME=simulated -n "$target" 2>/dev/null \
|
||||
| grep -F __backend \
|
||||
| sed 's/^[[:space:]]*//; s/[[:space:]]*$//; s/[[:space:]]\{1,\}/ /g' \
|
||||
|| true)
|
||||
@@ -57,7 +65,37 @@ 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'
|
||||
|
||||
# Static guard: no literal /dev/null inside $(shell ...).
|
||||
#
|
||||
# Unlike a recipe, which only ever runs on a platform its target supports,
|
||||
# $(shell ...) expands at parse time on EVERY invocation, through whatever shell
|
||||
# make picked. Under native cmd there is no /dev/null: cmd resolves the
|
||||
# redirection first, fails on the missing \dev directory, and hands the value to
|
||||
# the fallback — so the probe silently returns its default on Windows while
|
||||
# every POSIX host looks fine. Use $(NULL_REDIR), which carries the right token
|
||||
# per shell. This runs on the Linux CI, where the Windows branch is otherwise
|
||||
# never exercised.
|
||||
# Fail closed: grep exits 1 for "no matches" but >1 for a real scan error, and
|
||||
# swallowing the difference would let an unreadable or moved Makefile report ok.
|
||||
grep_status=0
|
||||
# `#` starts a comment anywhere in a Makefile line, so the match must not span
|
||||
# one: [^#]* keeps a prose mention like `FOO = bar # $(shell x 2>/dev/null)`
|
||||
# from failing CI, and cannot reach past a whole-line comment either. It matches
|
||||
# empty so a bare `$(shell ...)` in column 0 still counts.
|
||||
devnull_hits=$(grep -nE '^[[:space:]]*[^#]*\$\(shell[^#]*/dev/null' \
|
||||
"$backend_dir/../../Makefile" "$backend_dir/Makefile") || grep_status=$?
|
||||
if [ "$grep_status" -gt 1 ]; then
|
||||
printf 'FAIL %-16s unable to scan Makefiles (grep exit %s)\n' "static" "$grep_status"
|
||||
status=1
|
||||
elif [ -n "$devnull_hits" ]; then
|
||||
printf 'FAIL %-16s literal /dev/null inside $(shell ...) — use $(NULL_REDIR)\n' "static"
|
||||
printf ' %s\n' "$devnull_hits"
|
||||
status=1
|
||||
else
|
||||
printf 'ok %-16s %s\n' "static" 'no literal /dev/null inside $(shell ...)'
|
||||
fi
|
||||
|
||||
if [ "$status" -eq 0 ]; then
|
||||
echo "All Makefile shell-dispatch checks passed (3 shells x 3 targets)."
|
||||
echo "All Makefile shell-dispatch checks passed (3 shells x 3 targets, plus static guard)."
|
||||
fi
|
||||
exit "$status"
|
||||
|
||||
Reference in New Issue
Block a user