From 83c79087cdf8d5680caea54a18603d7859b44d45 Mon Sep 17 00:00:00 2001 From: SiYue <2835601846@qq.com> Date: Sun, 12 Apr 2026 21:27:40 +0800 Subject: [PATCH] Fix Windows build flow --- .gitignore | 4 + Makefile | 95 ++++++++++++++++++++++-- cmd/picoclaw/internal/onboard/command.go | 2 +- scripts/copydir.go | 84 +++++++++++++++++++++ web/Makefile | 88 +++++++++++++++++++--- 5 files changed, 256 insertions(+), 17 deletions(-) create mode 100644 scripts/copydir.go diff --git a/.gitignore b/.gitignore index 135867842..e1736f56b 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,10 @@ dist/ # Windows Application Icon/Resource *.syso +.cache/ +web/frontend/.pnpm-store/ +_tmp_* +web/frontend/_tmp_* # Test telegram integration cmd/telegram/ diff --git a/Makefile b/Makefile index beddd1138..6cc2e48c9 100644 --- a/Makefile +++ b/Makefile @@ -7,11 +7,29 @@ CMD_DIR=cmd/$(BINARY_NAME) MAIN_GO=$(CMD_DIR)/main.go EXT= +ifeq ($(OS),Windows_NT) + POWERSHELL=powershell -NoProfile -Command + GO=go + WEB_GO=go + 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 @@ -20,6 +38,12 @@ GO?=CGO_ENABLED=0 go WEB_GO?=$(GO) GO_BUILD_TAGS?=goolm,stdjson GOFLAGS?=-v -tags $(GO_BUILD_TAGS) +GOCACHE?=$(CURDIR)/.cache/go-build +GOMODCACHE?=$(CURDIR)/.cache/go-mod +GOTOOLCHAIN?=local +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,42 @@ 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 +222,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 +231,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 @@ -289,7 +366,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 diff --git a/cmd/picoclaw/internal/onboard/command.go b/cmd/picoclaw/internal/onboard/command.go index 4be19b2a5..bf8f4104f 100644 --- a/cmd/picoclaw/internal/onboard/command.go +++ b/cmd/picoclaw/internal/onboard/command.go @@ -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 diff --git a/scripts/copydir.go b/scripts/copydir.go new file mode 100644 index 000000000..74eff6c72 --- /dev/null +++ b/scripts/copydir.go @@ -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 \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() +} diff --git a/web/Makefile b/web/Makefile index cf5ea774a..b91c75c61 100644 --- a/web/Makefile +++ b/web/Makefile @@ -6,11 +6,18 @@ GO?=CGO_ENABLED=0 go WEB_GO?=$(GO) GO_BUILD_TAGS?=goolm,stdjson GOFLAGS?=-v -tags $(GO_BUILD_TAGS) +GOCACHE?=$(abspath ../.cache/go-build) +GOMODCACHE?=$(abspath ../.cache/go-mod) +GOTOOLCHAIN?=local +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 BACKEND_DIR=backend BACKEND_DIST=$(BACKEND_DIR)/dist @@ -18,18 +25,49 @@ PICOCLAW_BINARY_NAME=picoclaw PICOCLAW_BINARY?=$(abspath ../build/$(PICOCLAW_BINARY_NAME)) LAUNCHER_GUI_LDFLAG= +ifeq ($(OS),Windows_NT) + POWERSHELL=powershell -NoProfile -Command + GO=go + WEB_GO=go + 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) @@ -61,7 +99,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 @@ -90,33 +135,53 @@ 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) + @if not exist "$(FRONTEND_DIR)\node_modules" (echo Installing frontend dependencies... && cd $(FRONTEND_DIR) && pnpm install --frozen-lockfile --store-dir .pnpm-store) else echo Frontend dependencies already installed. +else @if [ ! -d $(FRONTEND_DIR)/node_modules ] || \ [ $(FRONTEND_DIR)/package.json -nt $(FRONTEND_DIR)/node_modules ] || \ [ $(FRONTEND_DIR)/pnpm-lock.yaml -nt $(FRONTEND_DIR)/node_modules ]; then \ echo "Installing frontend dependencies..."; \ cd $(FRONTEND_DIR) && pnpm install --frozen-lockfile; \ 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 @@ -131,5 +196,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