Fix Windows build flow (#2487)

* Fix Windows build flow

* build(makefile): make windows recipes shell-safe

- avoid backslash line-continuation in Windows build-launcher recipe

- replace cmd-specific if-not-exist with PowerShell check in web build-frontend

* Fix Windows build flow

* build(makefile): make windows recipes shell-safe

- avoid backslash line-continuation in Windows build-launcher recipe

- replace cmd-specific if-not-exist with PowerShell check in web build-frontend

* build(web): avoid shell-expanding powershell vars in windows recipe

- rewrite build-frontend Windows command without PowerShell local vars

- keep install-stamp hash check logic
This commit is contained in:
肆月
2026-04-24 17:50:59 +08:00
committed by GitHub
parent f334ac6d01
commit ccd19a48ce
5 changed files with 253 additions and 19 deletions
+4
View File
@@ -55,6 +55,10 @@ dist/
# Windows Application Icon/Resource
*.syso
.cache/
web/frontend/.pnpm-store/
_tmp_*
web/frontend/_tmp_*
# Test telegram integration
cmd/telegram/
+84 -8
View File
@@ -7,19 +7,43 @@ CMD_DIR=cmd/$(BINARY_NAME)
MAIN_GO=$(CMD_DIR)/main.go
EXT=
ifeq ($(OS),Windows_NT)
POWERSHELL=powershell -NoProfile -Command
WINDOWS_GOARCH_RAW:=$(strip $(shell go env GOARCH 2>NUL))
endif
# Version
VERSION?=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
GIT_COMMIT=$(shell git rev-parse --short=8 HEAD 2>/dev/null || echo "dev")
BUILD_TIME=$(shell date +%FT%T%z)
GO_VERSION=$(shell $(GO) version | awk '{print $$3}')
ifeq ($(OS),Windows_NT)
VERSION_RAW:=$(strip $(shell git describe --tags --always --dirty 2>NUL))
GIT_COMMIT_RAW:=$(strip $(shell git rev-parse --short=8 HEAD 2>NUL))
BUILD_TIME_RAW:=$(strip $(shell powershell -NoProfile -Command "Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK'"))
GO_VERSION_RAW:=$(strip $(shell go env GOVERSION 2>NUL))
else
VERSION_RAW:=$(strip $(shell git describe --tags --always --dirty 2>/dev/null))
GIT_COMMIT_RAW:=$(strip $(shell git rev-parse --short=8 HEAD 2>/dev/null))
BUILD_TIME_RAW:=$(strip $(shell date +%FT%T%z))
GO_VERSION_RAW:=$(strip $(shell go env GOVERSION 2>/dev/null))
endif
VERSION?=$(if $(VERSION_RAW),$(VERSION_RAW),dev)
GIT_COMMIT=$(if $(GIT_COMMIT_RAW),$(GIT_COMMIT_RAW),dev)
BUILD_TIME=$(if $(BUILD_TIME_RAW),$(BUILD_TIME_RAW),dev)
GO_VERSION=$(if $(GO_VERSION_RAW),$(GO_VERSION_RAW),unknown)
CONFIG_PKG=github.com/sipeed/picoclaw/pkg/config
LDFLAGS=-X $(CONFIG_PKG).Version=$(VERSION) -X $(CONFIG_PKG).GitCommit=$(GIT_COMMIT) -X $(CONFIG_PKG).BuildTime=$(BUILD_TIME) -X $(CONFIG_PKG).GoVersion=$(GO_VERSION) -s -w
# Go variables
GO?=CGO_ENABLED=0 go
GO?=go
WEB_GO?=$(GO)
CGO_ENABLED?=0
GO_BUILD_TAGS?=goolm,stdjson
GOFLAGS?=-v -tags $(GO_BUILD_TAGS)
GOCACHE?=$(CURDIR)/.cache/go-build
GOMODCACHE?=$(CURDIR)/.cache/go-mod
GOTOOLCHAIN?=local
export CGO_ENABLED
export GOCACHE
export GOMODCACHE
export GOTOOLCHAIN
comma:=,
empty:=
space:=$(empty) $(empty)
@@ -73,8 +97,21 @@ BUILTIN_SKILLS_DIR=$(CURDIR)/skills
LNCMD=ln -sf
# OS detection
UNAME_S?=$(shell uname -s)
UNAME_M?=$(shell uname -m)
ifeq ($(OS),Windows_NT)
UNAME_S=Windows
ifeq ($(WINDOWS_GOARCH_RAW),amd64)
UNAME_M=x86_64
else ifeq ($(WINDOWS_GOARCH_RAW),arm64)
UNAME_M=arm64
else ifeq ($(WINDOWS_GOARCH_RAW),386)
UNAME_M=x86
else
UNAME_M=$(if $(WINDOWS_GOARCH_RAW),$(WINDOWS_GOARCH_RAW),x86_64)
endif
else
UNAME_S?=$(shell uname -s)
UNAME_M?=$(shell uname -m)
endif
# Platform-specific settings
ifeq ($(UNAME_S),Linux)
@@ -122,6 +159,18 @@ else
endif
ifeq ($(OS),Windows_NT)
PLATFORM=windows
ifeq ($(UNAME_M),x86_64)
ARCH?=amd64
else ifeq ($(UNAME_M),arm64)
ARCH?=arm64
else
ARCH?=$(UNAME_M)
endif
EXT=.exe
endif
BINARY_PATH=$(BUILD_DIR)/$(BINARY_NAME)-$(PLATFORM)-$(ARCH)
# Default target
@@ -130,21 +179,37 @@ all: build
## generate: Run generate
generate:
@echo "Run generate..."
ifeq ($(OS),Windows_NT)
@$(POWERSHELL) "if (Test-Path -LiteralPath './$(CMD_DIR)/workspace') { Remove-Item -LiteralPath './$(CMD_DIR)/workspace' -Recurse -Force }"
else
@rm -r ./$(CMD_DIR)/workspace 2>/dev/null || true
endif
@$(GO) generate ./...
@echo "Run generate complete"
## build: Build the picoclaw binary for current platform
build: generate
@echo "Building $(BINARY_NAME)$(EXT) for $(PLATFORM)/$(ARCH)..."
ifeq ($(OS),Windows_NT)
@$(POWERSHELL) "New-Item -ItemType Directory -Force -Path '$(BUILD_DIR)' | Out-Null"
@$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BINARY_PATH)$(EXT) ./$(CMD_DIR)
@$(POWERSHELL) "Copy-Item -LiteralPath '$(BINARY_PATH)$(EXT)' -Destination '$(BUILD_DIR)/$(BINARY_NAME)$(EXT)' -Force"
else
@mkdir -p $(BUILD_DIR)
@GOARCH=${ARCH} $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BINARY_PATH)$(EXT) ./$(CMD_DIR)
@echo "Build complete: $(BINARY_PATH)$(EXT)"
@$(LNCMD) $(BINARY_NAME)-$(PLATFORM)-$(ARCH)$(EXT) $(BUILD_DIR)/$(BINARY_NAME)$(EXT)
endif
@echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)$(EXT)"
## build-launcher: Build the picoclaw-launcher (web console) binary
build-launcher:
@echo "Building picoclaw-launcher for $(PLATFORM)/$(ARCH)..."
ifeq ($(OS),Windows_NT)
@$(POWERSHELL) "New-Item -ItemType Directory -Force -Path '$(BUILD_DIR)' | Out-Null"
@$(MAKE) -C web build PLATFORM="$(PLATFORM)" ARCH="$(ARCH)" EXT="$(EXT)" OUTPUT="$(CURDIR)/$(BUILD_DIR)/picoclaw-launcher-$(PLATFORM)-$(ARCH)$(EXT)" GO_BUILD_TAGS="$(GO_BUILD_TAGS)"
@$(POWERSHELL) "Copy-Item -LiteralPath '$(BUILD_DIR)/picoclaw-launcher-$(PLATFORM)-$(ARCH)$(EXT)' -Destination '$(BUILD_DIR)/picoclaw-launcher$(EXT)' -Force"
else
@mkdir -p $(BUILD_DIR)
@GOARCH=${ARCH} $(MAKE) -C web build \
OUTPUT="$(CURDIR)/$(BUILD_DIR)/picoclaw-launcher-$(PLATFORM)-$(ARCH)$(EXT)" \
@@ -152,6 +217,7 @@ build-launcher:
GO_BUILD_TAGS='$(GO_BUILD_TAGS)' \
LDFLAGS='$(LDFLAGS)'
@$(LNCMD) picoclaw-launcher-$(PLATFORM)-$(ARCH)$(EXT) $(BUILD_DIR)/picoclaw-launcher$(EXT)
endif
@echo "Build complete: $(BUILD_DIR)/picoclaw-launcher$(EXT)"
build-launcher-frontend:
@@ -160,10 +226,16 @@ build-launcher-frontend:
## build-launcher-tui: Build the picoclaw-launcher TUI binary
build-launcher-tui:
@echo "Building picoclaw-launcher-tui for $(PLATFORM)/$(ARCH)..."
ifeq ($(OS),Windows_NT)
@$(POWERSHELL) "New-Item -ItemType Directory -Force -Path '$(BUILD_DIR)' | Out-Null"
@$(GO) build $(GOFLAGS) -o $(BUILD_DIR)/picoclaw-launcher-tui-$(PLATFORM)-$(ARCH)$(EXT) ./cmd/picoclaw-launcher-tui
@$(POWERSHELL) "Copy-Item -LiteralPath '$(BUILD_DIR)/picoclaw-launcher-tui-$(PLATFORM)-$(ARCH)$(EXT)' -Destination '$(BUILD_DIR)/picoclaw-launcher-tui$(EXT)' -Force"
else
@mkdir -p $(BUILD_DIR)
@$(GO) build $(GOFLAGS) -o $(BUILD_DIR)/picoclaw-launcher-tui-$(PLATFORM)-$(ARCH) ./cmd/picoclaw-launcher-tui
@ln -sf picoclaw-launcher-tui-$(PLATFORM)-$(ARCH) $(BUILD_DIR)/picoclaw-launcher-tui
@echo "Build complete: $(BUILD_DIR)/picoclaw-launcher-tui"
endif
@echo "Build complete: $(BUILD_DIR)/picoclaw-launcher-tui$(EXT)"
## build-whatsapp-native: Build with WhatsApp native (whatsmeow) support; larger binary
build-whatsapp-native: generate
@@ -290,7 +362,11 @@ uninstall-all:
## clean: Remove build artifacts
clean:
@echo "Cleaning build artifacts..."
ifeq ($(OS),Windows_NT)
@$(POWERSHELL) "if (Test-Path -LiteralPath '$(BUILD_DIR)') { Remove-Item -LiteralPath '$(BUILD_DIR)' -Recurse -Force }"
else
@rm -rf $(BUILD_DIR)
endif
@echo "Clean complete"
## vet: Run go vet for static analysis
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"github.com/spf13/cobra"
)
//go:generate cp -r ../../../../workspace .
//go:generate go run ../../../../scripts/copydir.go ../../../../workspace ./workspace
//go:embed workspace
var embeddedFiles embed.FS
+84
View File
@@ -0,0 +1,84 @@
package main
import (
"fmt"
"io"
"os"
"path/filepath"
)
func main() {
if len(os.Args) != 3 {
fmt.Fprintf(os.Stderr, "usage: go run scripts/copydir.go <src> <dst>\n")
os.Exit(2)
}
src := os.Args[1]
dst := os.Args[2]
if err := os.RemoveAll(dst); err != nil {
fmt.Fprintf(os.Stderr, "remove %s: %v\n", dst, err)
os.Exit(1)
}
if err := copyTree(src, dst); err != nil {
fmt.Fprintf(os.Stderr, "copy %s -> %s: %v\n", src, dst, err)
os.Exit(1)
}
}
func copyTree(src, dst string) error {
info, err := os.Stat(src)
if err != nil {
return err
}
if !info.IsDir() {
return fmt.Errorf("source is not a directory: %s", src)
}
return filepath.Walk(src, func(path string, entry os.FileInfo, walkErr error) error {
if walkErr != nil {
return walkErr
}
rel, err := filepath.Rel(src, path)
if err != nil {
return err
}
target := dst
if rel != "." {
target = filepath.Join(dst, rel)
}
if entry.IsDir() {
return os.MkdirAll(target, entry.Mode())
}
return copyFile(path, target, entry.Mode())
})
}
func copyFile(src, dst string, mode os.FileMode) error {
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
return err
}
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode)
if err != nil {
return err
}
defer out.Close()
if _, err := io.Copy(out, in); err != nil {
return err
}
return out.Close()
}
+80 -10
View File
@@ -2,15 +2,24 @@
build-android-arm64 build-android-bundle
# Go variables
GO?=CGO_ENABLED=0 go
GO?=go
WEB_GO?=$(GO)
CGO_ENABLED?=0
GO_BUILD_TAGS?=goolm,stdjson
GOFLAGS?=-v -tags $(GO_BUILD_TAGS)
GOCACHE?=$(abspath ../.cache/go-build)
GOMODCACHE?=$(abspath ../.cache/go-mod)
GOTOOLCHAIN?=local
export CGO_ENABLED
export GOCACHE
export GOMODCACHE
export GOTOOLCHAIN
# Build variables
BUILD_DIR=build
OUTPUT?=$(BUILD_DIR)/picoclaw-launcher
OUTPUT_ANDROID_ARM64?=$(BUILD_DIR)/picoclaw-launcher-android-arm64
EXT=
OUTPUT?=$(BUILD_DIR)/picoclaw-launcher$(EXT)
OUTPUT_ANDROID_ARM64?=$(BUILD_DIR)/picoclaw-launcher-android-arm64$(EXT)
FRONTEND_DIR=frontend
FRONTEND_INSTALL_STAMP=$(FRONTEND_DIR)/node_modules/.picoclaw-install-stamp
BACKEND_DIR=backend
@@ -19,18 +28,47 @@ PICOCLAW_BINARY_NAME=picoclaw
PICOCLAW_BINARY?=$(abspath ../build/$(PICOCLAW_BINARY_NAME))
LAUNCHER_GUI_LDFLAG=
ifeq ($(OS),Windows_NT)
POWERSHELL=powershell -NoProfile -Command
WINDOWS_GOARCH_RAW:=$(strip $(shell go env GOARCH 2>NUL))
endif
# Version
VERSION?=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
GIT_COMMIT=$(shell git rev-parse --short=8 HEAD 2>/dev/null || echo "dev")
BUILD_TIME=$(shell date +%FT%T%z)
GO_VERSION=$(shell $(WEB_GO) version | awk '{print $$3}')
ifeq ($(OS),Windows_NT)
VERSION_RAW:=$(strip $(shell git describe --tags --always --dirty 2>NUL))
GIT_COMMIT_RAW:=$(strip $(shell git rev-parse --short=8 HEAD 2>NUL))
BUILD_TIME_RAW:=$(strip $(shell powershell -NoProfile -Command "Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK'"))
GO_VERSION_RAW:=$(strip $(shell go env GOVERSION 2>NUL))
else
VERSION_RAW:=$(strip $(shell git describe --tags --always --dirty 2>/dev/null))
GIT_COMMIT_RAW:=$(strip $(shell git rev-parse --short=8 HEAD 2>/dev/null))
BUILD_TIME_RAW:=$(strip $(shell date +%FT%T%z))
GO_VERSION_RAW:=$(strip $(shell go env GOVERSION 2>/dev/null))
endif
VERSION?=$(if $(VERSION_RAW),$(VERSION_RAW),dev)
GIT_COMMIT=$(if $(GIT_COMMIT_RAW),$(GIT_COMMIT_RAW),dev)
BUILD_TIME=$(if $(BUILD_TIME_RAW),$(BUILD_TIME_RAW),dev)
GO_VERSION=$(if $(GO_VERSION_RAW),$(GO_VERSION_RAW),unknown)
CONFIG_PKG=github.com/sipeed/picoclaw/pkg/config
LDFLAGS=-X $(CONFIG_PKG).Version=$(VERSION) -X $(CONFIG_PKG).GitCommit=$(GIT_COMMIT) -X $(CONFIG_PKG).BuildTime=$(BUILD_TIME) -X $(CONFIG_PKG).GoVersion=$(GO_VERSION) -s -w
# OS detection
UNAME_S:=$(shell uname -s)
UNAME_M:=$(shell uname -m)
ifeq ($(OS),Windows_NT)
UNAME_S=Windows
ifeq ($(WINDOWS_GOARCH_RAW),amd64)
UNAME_M=x86_64
else ifeq ($(WINDOWS_GOARCH_RAW),arm64)
UNAME_M=arm64
else ifeq ($(WINDOWS_GOARCH_RAW),386)
UNAME_M=x86
else
UNAME_M=$(if $(WINDOWS_GOARCH_RAW),$(WINDOWS_GOARCH_RAW),x86_64)
endif
else
UNAME_S:=$(shell uname -s)
UNAME_M:=$(shell uname -m)
endif
# Platform-specific settings
ifeq ($(UNAME_S),Linux)
@@ -62,7 +100,14 @@ else ifeq ($(UNAME_S),Darwin)
endif
else ifeq ($(UNAME_S),Windows)
PLATFORM=windows
ARCH=$(UNAME_M)
ifeq ($(UNAME_M),x86_64)
ARCH=amd64
else ifeq ($(UNAME_M),arm64)
ARCH=arm64
else
ARCH=$(UNAME_M)
endif
EXT=.exe
PICOCLAW_BINARY_NAME=picoclaw.exe
LAUNCHER_GUI_LDFLAG=-H=windowsgui
else
@@ -91,21 +136,36 @@ dev-backend:
# Build frontend and embed into Go binary
build: build-frontend
ifeq ($(OS),Windows_NT)
@$(POWERSHELL) "New-Item -ItemType Directory -Force -Path (Split-Path -Parent '$(OUTPUT)') | Out-Null"
else
@mkdir -p "$$(dirname "$(OUTPUT)")"
endif
${WEB_GO} build $(GOFLAGS) -ldflags "$(LAUNCHER_LDFLAGS)" -o "$(OUTPUT)" ./$(BACKEND_DIR)/
# Build launcher for Android ARM64 (frontend must already be built)
build-android-arm64: build-frontend
ifeq ($(OS),Windows_NT)
@$(POWERSHELL) "New-Item -ItemType Directory -Force -Path '$(BUILD_DIR)' | Out-Null"
else
@mkdir -p $(BUILD_DIR)
endif
GOOS=android GOARCH=arm64 $(GO) build -tags stdjson -ldflags "$(LDFLAGS)" -o "$(OUTPUT_ANDROID_ARM64)" ./$(BACKEND_DIR)/
# Build launcher for all Android architectures
build-android-bundle: build-frontend
ifeq ($(OS),Windows_NT)
@$(POWERSHELL) "New-Item -ItemType Directory -Force -Path '$(BUILD_DIR)' | Out-Null"
else
@mkdir -p $(BUILD_DIR)
endif
GOOS=android GOARCH=arm64 $(GO) build -tags stdjson -ldflags "$(LDFLAGS)" -o "$(BUILD_DIR)/picoclaw-launcher-android-arm64" ./$(BACKEND_DIR)/
@echo "All Android launcher builds complete"
build-frontend:
ifeq ($(OS),Windows_NT)
@$(POWERSHELL) "if ((-not (Test-Path -LiteralPath '$(FRONTEND_DIR)/node_modules')) -or (-not (Test-Path -LiteralPath '$(FRONTEND_DIR)/node_modules/.bin/tsc')) -or (-not (Test-Path -LiteralPath '$(FRONTEND_INSTALL_STAMP)')) -or ((Get-Content -LiteralPath '$(FRONTEND_INSTALL_STAMP)' -Raw).Trim() -ne (((Get-FileHash -LiteralPath '$(FRONTEND_DIR)/package.json' -Algorithm SHA256).Hash + ':' + (Get-FileHash -LiteralPath '$(FRONTEND_DIR)/pnpm-lock.yaml' -Algorithm SHA256).Hash)))) { Write-Host 'Installing frontend dependencies...'; Push-Location '$(FRONTEND_DIR)'; try { pnpm install --frozen-lockfile } finally { Pop-Location }; Set-Content -LiteralPath '$(FRONTEND_INSTALL_STAMP)' -Value (((Get-FileHash -LiteralPath '$(FRONTEND_DIR)/package.json' -Algorithm SHA256).Hash + ':' + (Get-FileHash -LiteralPath '$(FRONTEND_DIR)/pnpm-lock.yaml' -Algorithm SHA256).Hash)) -NoNewline }"
else
@expected_stamp="$$(cat $(FRONTEND_DIR)/package.json $(FRONTEND_DIR)/pnpm-lock.yaml | cksum | awk '{print $$1 ":" $$2}')"; \
if [ ! -d $(FRONTEND_DIR)/node_modules ] || \
[ ! -x $(FRONTEND_DIR)/node_modules/.bin/tsc ] || \
@@ -115,12 +175,17 @@ build-frontend:
(cd $(FRONTEND_DIR) && CI=true pnpm install --frozen-lockfile) && \
printf '%s\n' "$$expected_stamp" > $(FRONTEND_INSTALL_STAMP); \
fi
endif
@echo "Building frontend..."
@cd $(FRONTEND_DIR) && pnpm build:backend
build-dev-picoclaw:
@echo "Building picoclaw for launcher development..."
ifeq ($(OS),Windows_NT)
@$(POWERSHELL) "New-Item -ItemType Directory -Force -Path (Split-Path -Parent '$(PICOCLAW_BINARY)') | Out-Null"
else
@mkdir -p "$$(dirname "$(PICOCLAW_BINARY)")"
endif
@$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o "$(PICOCLAW_BINARY)" ../cmd/picoclaw
# Run all tests
@@ -135,5 +200,10 @@ lint:
# Clean build artifacts
clean:
ifeq ($(OS),Windows_NT)
@$(POWERSHELL) "$$paths=@('$(FRONTEND_DIR)/dist','$(BACKEND_DIST)','$(BUILD_DIR)'); foreach($$p in $$paths){ if (Test-Path -LiteralPath $$p) { Remove-Item -LiteralPath $$p -Recurse -Force } }"
@node $(FRONTEND_DIR)/scripts/ensure-backend-gitkeep.cjs
else
rm -rf $(FRONTEND_DIR)/dist $(BACKEND_DIST) $(BUILD_DIR)
node $(FRONTEND_DIR)/scripts/ensure-backend-gitkeep.cjs
endif