Browse Source
chore: Refactor build and installation scripts; update configuration and release process
chore: Refactor build and installation scripts; update configuration and release process
- Enhanced build scripts for improved parallel execution and optimization flags. - Updated installation scripts for better user experience and error handling. - Modified .gitignore to include new build artifacts and IDE configurations. - Updated .goreleaser.yml for better release management and platform support. - Removed deprecated main.go file and adjusted README for clarity on installation and usage. - Added support for multiple architectures in build process, including 32-bit and 64-bit for Windows, macOS, and Linux. These changes streamline the development workflow and enhance the overall usability of the Cursor ID Modifier tool.pull/85/head
19 changed files with 1336 additions and 1884 deletions
-
86.github/workflows/release.yml
-
43.gitignore
-
106.goreleaser.yml
-
21Makefile
-
164README.md
-
9go.mod
-
17go.sum
-
150internal/config/config.go
-
156internal/lang/lang.go
-
162internal/process/manager.go
-
101internal/ui/display.go
-
110internal/ui/spinner.go
-
1050main.go
-
95pkg/idgen/generator.go
-
26pkg/idgen/generator_test.go
-
144scripts/build_all.bat
-
108scripts/build_all.sh
-
318scripts/install.ps1
-
320scripts/install.sh
@ -1,23 +1,40 @@ |
|||||
# Binary files |
|
||||
*.dll |
|
||||
*.so |
|
||||
*.dylib |
|
||||
|
|
||||
# Build directories |
|
||||
releases/ |
|
||||
|
# Compiled binary |
||||
cursor-id-modifier |
cursor-id-modifier |
||||
|
cursor-id-modifier.exe |
||||
|
|
||||
|
# Build output directories |
||||
|
bin/ |
||||
|
dist/ |
||||
|
|
||||
# Go specific |
# Go specific |
||||
go.sum |
go.sum |
||||
|
|
||||
# Temporary files |
|
||||
*.tmp |
|
||||
*~ |
|
||||
|
# IDE and editor files |
||||
|
.vscode/ |
||||
|
.idea/ |
||||
|
*.swp |
||||
|
*.swo |
||||
|
|
||||
# System files |
|
||||
|
# OS specific |
||||
.DS_Store |
.DS_Store |
||||
Thumbs.db |
Thumbs.db |
||||
|
|
||||
.vscode |
|
||||
/.idea |
|
||||
|
# Build and release artifacts |
||||
|
releases/ |
||||
|
*.syso |
||||
|
*.exe |
||||
|
*.exe~ |
||||
|
*.dll |
||||
|
*.so |
||||
|
*.dylib |
||||
|
|
||||
|
# Test files |
||||
|
*.test |
||||
|
*.out |
||||
|
coverage.txt |
||||
|
|
||||
|
# Temporary files |
||||
|
*.tmp |
||||
|
*~ |
||||
|
*.bak |
||||
|
*.log |
@ -1,56 +1,110 @@ |
|||||
project_name: cursor-id-modifier |
|
||||
|
|
||||
before: |
before: |
||||
hooks: |
hooks: |
||||
- go mod tidy |
- go mod tidy |
||||
|
|
||||
builds: |
builds: |
||||
- env: |
|
||||
|
- main: ./cmd/cursor-id-modifier |
||||
|
binary: cursor-id-modifier |
||||
|
env: |
||||
- CGO_ENABLED=0 |
- CGO_ENABLED=0 |
||||
ldflags: |
|
||||
- -s -w -X main.version={{.Version}} |
|
||||
goos: |
goos: |
||||
- linux |
- linux |
||||
- windows |
- windows |
||||
- darwin |
- darwin |
||||
goarch: |
goarch: |
||||
- amd64 |
|
||||
- arm64 |
|
||||
mod_timestamp: '{{ .CommitTimestamp }}' |
|
||||
|
- amd64 # Intel 64-bit |
||||
|
- arm64 # Apple Silicon/ARM64 |
||||
|
- "386" # Intel 32-bit |
||||
|
ignore: |
||||
|
- goos: darwin |
||||
|
goarch: "386" # No 32-bit support for macOS |
||||
|
ldflags: |
||||
|
- -s -w |
||||
|
- -X main.version={{.Version}} |
||||
flags: |
flags: |
||||
- -trimpath |
- -trimpath |
||||
binary: cursor_id_modifier_{{ .Version }}_{{ .Os }}_{{ .Arch }} |
|
||||
|
mod_timestamp: '{{ .CommitTimestamp }}' |
||||
|
|
||||
|
# Build matrix |
||||
|
matrix: |
||||
|
# Special builds for macOS |
||||
|
- goos: [darwin] |
||||
|
goarch: [amd64] |
||||
|
tags: ["intel"] |
||||
|
- goos: [darwin] |
||||
|
goarch: [arm64] |
||||
|
tags: ["apple_silicon"] |
||||
|
# Windows builds |
||||
|
- goos: [windows] |
||||
|
goarch: [amd64, "386"] |
||||
|
# Linux builds |
||||
|
- goos: [linux] |
||||
|
goarch: [amd64, arm64, "386"] |
||||
|
|
||||
archives: |
archives: |
||||
- format: binary |
|
||||
name_template: "{{ .Binary }}" |
|
||||
allow_different_binary_count: true |
|
||||
|
- format: tar.gz |
||||
|
format_overrides: |
||||
|
- goos: windows |
||||
|
format: zip |
||||
|
name_template: >- |
||||
|
{{ .ProjectName }}_ |
||||
|
{{- .Version }}_ |
||||
|
{{- .Os }}_ |
||||
|
{{- .Arch }} |
||||
|
{{- with .Tags }}_{{ . }}{{ end }} |
||||
|
files: |
||||
|
- README.md |
||||
|
- LICENSE |
||||
|
- scripts/* # Include installation scripts |
||||
|
replacements: |
||||
|
darwin: macOS |
||||
|
linux: Linux |
||||
|
windows: Windows |
||||
|
386: x86 |
||||
|
amd64: x64 |
||||
|
arm64: arm64 |
||||
|
|
||||
|
checksum: |
||||
|
name_template: 'checksums.txt' |
||||
|
algorithm: sha256 |
||||
|
|
||||
changelog: |
changelog: |
||||
use: github |
|
||||
sort: asc |
sort: asc |
||||
groups: |
|
||||
- title: Features |
|
||||
regexp: "^.*feat[(\\w)]*:+.*$" |
|
||||
order: 0 |
|
||||
- title: 'Bug Fixes' |
|
||||
regexp: "^.*fix[(\\w)]*:+.*$" |
|
||||
order: 1 |
|
||||
- title: Others |
|
||||
order: 999 |
|
||||
filters: |
filters: |
||||
exclude: |
exclude: |
||||
- '^docs:' |
- '^docs:' |
||||
- '^test:' |
- '^test:' |
||||
- '^ci:' |
- '^ci:' |
||||
- '^chore:' |
|
||||
- Merge pull request |
- Merge pull request |
||||
- Merge branch |
- Merge branch |
||||
|
|
||||
release: |
release: |
||||
github: |
|
||||
owner: '{{ .Env.GITHUB_REPOSITORY_OWNER }}' |
|
||||
name: '{{ .Env.GITHUB_REPOSITORY_NAME }}' |
|
||||
draft: false |
draft: false |
||||
prerelease: auto |
prerelease: auto |
||||
mode: replace |
mode: replace |
||||
|
header: | |
||||
|
## Cursor ID Modifier {{ .Version }} |
||||
|
|
||||
|
### Supported Platforms |
||||
|
- Windows: x64, x86 |
||||
|
- macOS: Intel (x64), Apple Silicon (M1/M2) |
||||
|
- Linux: x64, x86, ARM64 |
||||
|
|
||||
|
See [CHANGELOG](CHANGELOG.md) for more details. |
||||
|
footer: | |
||||
|
**Full Changelog**: https://github.com/dacrab/cursor-id-modifier/compare/{{ .PreviousTag }}...{{ .Tag }} |
||||
|
|
||||
|
## Quick Installation |
||||
|
|
||||
|
**Linux/macOS**: |
||||
|
```bash |
||||
|
curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier |
||||
|
``` |
||||
|
|
||||
|
**Windows** (PowerShell Admin): |
||||
|
```powershell |
||||
|
irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier |
||||
|
``` |
||||
|
|
||||
|
snapshot: |
||||
|
name_template: "{{ incpatch .Version }}-next" |
@ -0,0 +1,21 @@ |
|||||
|
.PHONY: build clean test vet |
||||
|
|
||||
|
# Build the application
|
||||
|
build: |
||||
|
go build -v ./cmd/cursor-id-modifier |
||||
|
|
||||
|
# Clean build artifacts
|
||||
|
clean: |
||||
|
rm -f cursor-id-modifier |
||||
|
go clean |
||||
|
|
||||
|
# Run tests
|
||||
|
test: |
||||
|
go test -v ./... |
||||
|
|
||||
|
# Run go vet
|
||||
|
vet: |
||||
|
go vet ./... |
||||
|
|
||||
|
# Run all checks
|
||||
|
all: vet test build |
@ -0,0 +1,150 @@ |
|||||
|
package config |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"os" |
||||
|
"path/filepath" |
||||
|
"runtime" |
||||
|
"sync" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
// StorageConfig represents the storage configuration
|
||||
|
type StorageConfig struct { |
||||
|
TelemetryMacMachineId string `json:"telemetry.macMachineId"` |
||||
|
TelemetryMachineId string `json:"telemetry.machineId"` |
||||
|
TelemetryDevDeviceId string `json:"telemetry.devDeviceId"` |
||||
|
TelemetrySqmId string `json:"telemetry.sqmId"` |
||||
|
LastModified string `json:"lastModified"` |
||||
|
Version string `json:"version"` |
||||
|
} |
||||
|
|
||||
|
// Manager handles configuration operations
|
||||
|
type Manager struct { |
||||
|
configPath string |
||||
|
mu sync.RWMutex |
||||
|
} |
||||
|
|
||||
|
// NewManager creates a new configuration manager
|
||||
|
func NewManager(username string) (*Manager, error) { |
||||
|
configPath, err := getConfigPath(username) |
||||
|
if err != nil { |
||||
|
return nil, fmt.Errorf("failed to get config path: %w", err) |
||||
|
} |
||||
|
|
||||
|
return &Manager{ |
||||
|
configPath: configPath, |
||||
|
}, nil |
||||
|
} |
||||
|
|
||||
|
// ReadConfig reads the existing configuration
|
||||
|
func (m *Manager) ReadConfig() (*StorageConfig, error) { |
||||
|
m.mu.RLock() |
||||
|
defer m.mu.RUnlock() |
||||
|
|
||||
|
data, err := os.ReadFile(m.configPath) |
||||
|
if err != nil { |
||||
|
if os.IsNotExist(err) { |
||||
|
return nil, nil |
||||
|
} |
||||
|
return nil, fmt.Errorf("failed to read config file: %w", err) |
||||
|
} |
||||
|
|
||||
|
var config StorageConfig |
||||
|
if err := json.Unmarshal(data, &config); err != nil { |
||||
|
return nil, fmt.Errorf("failed to parse config file: %w", err) |
||||
|
} |
||||
|
|
||||
|
return &config, nil |
||||
|
} |
||||
|
|
||||
|
// SaveConfig saves the configuration
|
||||
|
func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error { |
||||
|
m.mu.Lock() |
||||
|
defer m.mu.Unlock() |
||||
|
|
||||
|
// Ensure parent directories exist
|
||||
|
if err := os.MkdirAll(filepath.Dir(m.configPath), 0755); err != nil { |
||||
|
return fmt.Errorf("failed to create config directory: %w", err) |
||||
|
} |
||||
|
|
||||
|
// Set file permissions
|
||||
|
if err := os.Chmod(m.configPath, 0666); err != nil && !os.IsNotExist(err) { |
||||
|
return fmt.Errorf("failed to set file permissions: %w", err) |
||||
|
} |
||||
|
|
||||
|
// Read existing config to preserve other fields
|
||||
|
var originalFile map[string]interface{} |
||||
|
originalFileContent, err := os.ReadFile(m.configPath) |
||||
|
if err != nil && !os.IsNotExist(err) { |
||||
|
return fmt.Errorf("failed to read original file: %w", err) |
||||
|
} else if err == nil { |
||||
|
if err := json.Unmarshal(originalFileContent, &originalFile); err != nil { |
||||
|
return fmt.Errorf("failed to parse original file: %w", err) |
||||
|
} |
||||
|
} else { |
||||
|
originalFile = make(map[string]interface{}) |
||||
|
} |
||||
|
|
||||
|
// Update fields
|
||||
|
originalFile["telemetry.sqmId"] = config.TelemetrySqmId |
||||
|
originalFile["telemetry.macMachineId"] = config.TelemetryMacMachineId |
||||
|
originalFile["telemetry.machineId"] = config.TelemetryMachineId |
||||
|
originalFile["telemetry.devDeviceId"] = config.TelemetryDevDeviceId |
||||
|
originalFile["lastModified"] = time.Now().UTC().Format(time.RFC3339) |
||||
|
originalFile["version"] = "1.0.1" |
||||
|
|
||||
|
// Marshal with indentation
|
||||
|
newFileContent, err := json.MarshalIndent(originalFile, "", " ") |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to marshal config: %w", err) |
||||
|
} |
||||
|
|
||||
|
// Write to temporary file
|
||||
|
tmpPath := m.configPath + ".tmp" |
||||
|
if err := os.WriteFile(tmpPath, newFileContent, 0666); err != nil { |
||||
|
return fmt.Errorf("failed to write temporary file: %w", err) |
||||
|
} |
||||
|
|
||||
|
// Set final permissions
|
||||
|
fileMode := os.FileMode(0666) |
||||
|
if readOnly { |
||||
|
fileMode = 0444 |
||||
|
} |
||||
|
|
||||
|
if err := os.Chmod(tmpPath, fileMode); err != nil { |
||||
|
os.Remove(tmpPath) |
||||
|
return fmt.Errorf("failed to set temporary file permissions: %w", err) |
||||
|
} |
||||
|
|
||||
|
// Atomic rename
|
||||
|
if err := os.Rename(tmpPath, m.configPath); err != nil { |
||||
|
os.Remove(tmpPath) |
||||
|
return fmt.Errorf("failed to rename file: %w", err) |
||||
|
} |
||||
|
|
||||
|
// Sync directory
|
||||
|
if dir, err := os.Open(filepath.Dir(m.configPath)); err == nil { |
||||
|
dir.Sync() |
||||
|
dir.Close() |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// getConfigPath returns the path to the configuration file
|
||||
|
func getConfigPath(username string) (string, error) { |
||||
|
var configDir string |
||||
|
switch runtime.GOOS { |
||||
|
case "windows": |
||||
|
configDir = filepath.Join(os.Getenv("APPDATA"), "Cursor", "User", "globalStorage") |
||||
|
case "darwin": |
||||
|
configDir = filepath.Join("/Users", username, "Library", "Application Support", "Cursor", "User", "globalStorage") |
||||
|
case "linux": |
||||
|
configDir = filepath.Join("/home", username, ".config", "Cursor", "User", "globalStorage") |
||||
|
default: |
||||
|
return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS) |
||||
|
} |
||||
|
return filepath.Join(configDir, "storage.json"), nil |
||||
|
} |
@ -0,0 +1,156 @@ |
|||||
|
package lang |
||||
|
|
||||
|
import ( |
||||
|
"os" |
||||
|
"os/exec" |
||||
|
"strings" |
||||
|
"sync" |
||||
|
) |
||||
|
|
||||
|
// Language represents a supported language
|
||||
|
type Language string |
||||
|
|
||||
|
const ( |
||||
|
// CN represents Chinese language
|
||||
|
CN Language = "cn" |
||||
|
// EN represents English language
|
||||
|
EN Language = "en" |
||||
|
) |
||||
|
|
||||
|
// TextResource contains all translatable text resources
|
||||
|
type TextResource struct { |
||||
|
SuccessMessage string |
||||
|
RestartMessage string |
||||
|
ReadingConfig string |
||||
|
GeneratingIds string |
||||
|
PressEnterToExit string |
||||
|
ErrorPrefix string |
||||
|
PrivilegeError string |
||||
|
RunAsAdmin string |
||||
|
RunWithSudo string |
||||
|
SudoExample string |
||||
|
ConfigLocation string |
||||
|
CheckingProcesses string |
||||
|
ClosingProcesses string |
||||
|
ProcessesClosed string |
||||
|
PleaseWait string |
||||
|
SetReadOnlyMessage string |
||||
|
} |
||||
|
|
||||
|
var ( |
||||
|
currentLanguage Language |
||||
|
currentLanguageOnce sync.Once |
||||
|
languageMutex sync.RWMutex |
||||
|
) |
||||
|
|
||||
|
// GetCurrentLanguage returns the current language, detecting it if not already set
|
||||
|
func GetCurrentLanguage() Language { |
||||
|
currentLanguageOnce.Do(func() { |
||||
|
currentLanguage = detectLanguage() |
||||
|
}) |
||||
|
|
||||
|
languageMutex.RLock() |
||||
|
defer languageMutex.RUnlock() |
||||
|
return currentLanguage |
||||
|
} |
||||
|
|
||||
|
// SetLanguage sets the current language
|
||||
|
func SetLanguage(lang Language) { |
||||
|
languageMutex.Lock() |
||||
|
defer languageMutex.Unlock() |
||||
|
currentLanguage = lang |
||||
|
} |
||||
|
|
||||
|
// GetText returns the TextResource for the current language
|
||||
|
func GetText() TextResource { |
||||
|
return texts[GetCurrentLanguage()] |
||||
|
} |
||||
|
|
||||
|
// detectLanguage detects the system language
|
||||
|
func detectLanguage() Language { |
||||
|
// Check environment variables
|
||||
|
for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} { |
||||
|
if lang := os.Getenv(envVar); lang != "" && strings.Contains(strings.ToLower(lang), "zh") { |
||||
|
return CN |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Check Windows language settings
|
||||
|
if isWindows() { |
||||
|
if isWindowsChineseLocale() { |
||||
|
return CN |
||||
|
} |
||||
|
} else { |
||||
|
// Check Unix locale
|
||||
|
if isUnixChineseLocale() { |
||||
|
return CN |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return EN |
||||
|
} |
||||
|
|
||||
|
func isWindows() bool { |
||||
|
return os.Getenv("OS") == "Windows_NT" |
||||
|
} |
||||
|
|
||||
|
func isWindowsChineseLocale() bool { |
||||
|
// Check Windows UI culture
|
||||
|
cmd := exec.Command("powershell", "-Command", |
||||
|
"[System.Globalization.CultureInfo]::CurrentUICulture.Name") |
||||
|
output, err := cmd.Output() |
||||
|
if err == nil && strings.HasPrefix(strings.ToLower(strings.TrimSpace(string(output))), "zh") { |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
// Check Windows locale
|
||||
|
cmd = exec.Command("wmic", "os", "get", "locale") |
||||
|
output, err = cmd.Output() |
||||
|
return err == nil && strings.Contains(string(output), "2052") |
||||
|
} |
||||
|
|
||||
|
func isUnixChineseLocale() bool { |
||||
|
cmd := exec.Command("locale") |
||||
|
output, err := cmd.Output() |
||||
|
return err == nil && strings.Contains(strings.ToLower(string(output)), "zh_cn") |
||||
|
} |
||||
|
|
||||
|
// texts contains all translations
|
||||
|
var texts = map[Language]TextResource{ |
||||
|
CN: { |
||||
|
SuccessMessage: "[√] 配置文件已成功更新!", |
||||
|
RestartMessage: "[!] 请手动重启 Cursor 以使更新生效", |
||||
|
ReadingConfig: "正在读取配置文件...", |
||||
|
GeneratingIds: "正在生成新的标识符...", |
||||
|
PressEnterToExit: "按回车键退出程序...", |
||||
|
ErrorPrefix: "程序发生严重错误: %v", |
||||
|
PrivilegeError: "\n[!] 错误:需要管理员权限", |
||||
|
RunAsAdmin: "请右键点击程序,选择「以管理员身份运行」", |
||||
|
RunWithSudo: "请使用 sudo 命令运行此程序", |
||||
|
SudoExample: "示例: sudo %s", |
||||
|
ConfigLocation: "配置文件位置:", |
||||
|
CheckingProcesses: "正在检查运行中的 Cursor 实例...", |
||||
|
ClosingProcesses: "正在关闭 Cursor 实例...", |
||||
|
ProcessesClosed: "所有 Cursor 实例已关闭", |
||||
|
PleaseWait: "请稍候...", |
||||
|
SetReadOnlyMessage: "设置 storage.json 为只读模式, 这将导致 workspace 记录信息丢失等问题", |
||||
|
}, |
||||
|
EN: { |
||||
|
SuccessMessage: "[√] Configuration file updated successfully!", |
||||
|
RestartMessage: "[!] Please restart Cursor manually for changes to take effect", |
||||
|
ReadingConfig: "Reading configuration file...", |
||||
|
GeneratingIds: "Generating new identifiers...", |
||||
|
PressEnterToExit: "Press Enter to exit...", |
||||
|
ErrorPrefix: "Program encountered a serious error: %v", |
||||
|
PrivilegeError: "\n[!] Error: Administrator privileges required", |
||||
|
RunAsAdmin: "Please right-click and select 'Run as Administrator'", |
||||
|
RunWithSudo: "Please run this program with sudo", |
||||
|
SudoExample: "Example: sudo %s", |
||||
|
ConfigLocation: "Config file location:", |
||||
|
CheckingProcesses: "Checking for running Cursor instances...", |
||||
|
ClosingProcesses: "Closing Cursor instances...", |
||||
|
ProcessesClosed: "All Cursor instances have been closed", |
||||
|
PleaseWait: "Please wait...", |
||||
|
SetReadOnlyMessage: "Set storage.json to read-only mode, which will cause issues such as lost workspace records", |
||||
|
}, |
||||
|
} |
@ -0,0 +1,162 @@ |
|||||
|
package process |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"fmt" |
||||
|
"os/exec" |
||||
|
"runtime" |
||||
|
"strings" |
||||
|
"sync" |
||||
|
"time" |
||||
|
|
||||
|
"github.com/sirupsen/logrus" |
||||
|
) |
||||
|
|
||||
|
// Config holds process manager configuration
|
||||
|
type Config struct { |
||||
|
RetryAttempts int |
||||
|
RetryDelay time.Duration |
||||
|
Timeout time.Duration |
||||
|
} |
||||
|
|
||||
|
// DefaultConfig returns the default configuration
|
||||
|
func DefaultConfig() *Config { |
||||
|
return &Config{ |
||||
|
RetryAttempts: 3, |
||||
|
RetryDelay: time.Second, |
||||
|
Timeout: 30 * time.Second, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Manager handles process-related operations
|
||||
|
type Manager struct { |
||||
|
config *Config |
||||
|
log *logrus.Logger |
||||
|
mu sync.Mutex |
||||
|
} |
||||
|
|
||||
|
// NewManager creates a new process manager
|
||||
|
func NewManager(config *Config, log *logrus.Logger) *Manager { |
||||
|
if config == nil { |
||||
|
config = DefaultConfig() |
||||
|
} |
||||
|
if log == nil { |
||||
|
log = logrus.New() |
||||
|
} |
||||
|
return &Manager{ |
||||
|
config: config, |
||||
|
log: log, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// KillCursorProcesses attempts to kill all Cursor processes
|
||||
|
func (m *Manager) KillCursorProcesses() error { |
||||
|
m.mu.Lock() |
||||
|
defer m.mu.Unlock() |
||||
|
|
||||
|
ctx, cancel := context.WithTimeout(context.Background(), m.config.Timeout) |
||||
|
defer cancel() |
||||
|
|
||||
|
for attempt := 0; attempt < m.config.RetryAttempts; attempt++ { |
||||
|
m.log.Debugf("Attempt %d/%d to kill Cursor processes", attempt+1, m.config.RetryAttempts) |
||||
|
|
||||
|
if err := m.killProcess(ctx); err != nil { |
||||
|
m.log.Warnf("Failed to kill processes on attempt %d: %v", attempt+1, err) |
||||
|
time.Sleep(m.config.RetryDelay) |
||||
|
continue |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
return fmt.Errorf("failed to kill all Cursor processes after %d attempts", m.config.RetryAttempts) |
||||
|
} |
||||
|
|
||||
|
// IsCursorRunning checks if any Cursor process is running
|
||||
|
func (m *Manager) IsCursorRunning() bool { |
||||
|
m.mu.Lock() |
||||
|
defer m.mu.Unlock() |
||||
|
|
||||
|
processes, err := m.listCursorProcesses() |
||||
|
if err != nil { |
||||
|
m.log.Warnf("Failed to list Cursor processes: %v", err) |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
return len(processes) > 0 |
||||
|
} |
||||
|
|
||||
|
func (m *Manager) killProcess(ctx context.Context) error { |
||||
|
if runtime.GOOS == "windows" { |
||||
|
return m.killWindowsProcess(ctx) |
||||
|
} |
||||
|
return m.killUnixProcess(ctx) |
||||
|
} |
||||
|
|
||||
|
func (m *Manager) killWindowsProcess(ctx context.Context) error { |
||||
|
// First try graceful termination
|
||||
|
if err := exec.CommandContext(ctx, "taskkill", "/IM", "Cursor.exe").Run(); err != nil { |
||||
|
m.log.Debugf("Graceful termination failed: %v", err) |
||||
|
} |
||||
|
|
||||
|
time.Sleep(m.config.RetryDelay) |
||||
|
|
||||
|
// Force kill if still running
|
||||
|
if err := exec.CommandContext(ctx, "taskkill", "/F", "/IM", "Cursor.exe").Run(); err != nil { |
||||
|
return fmt.Errorf("failed to force kill Cursor process: %w", err) |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (m *Manager) killUnixProcess(ctx context.Context) error { |
||||
|
processes, err := m.listCursorProcesses() |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to list processes: %w", err) |
||||
|
} |
||||
|
|
||||
|
for _, pid := range processes { |
||||
|
if err := m.forceKillProcess(ctx, pid); err != nil { |
||||
|
m.log.Warnf("Failed to kill process %s: %v", pid, err) |
||||
|
continue |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (m *Manager) forceKillProcess(ctx context.Context, pid string) error { |
||||
|
// Try graceful termination first
|
||||
|
if err := exec.CommandContext(ctx, "kill", pid).Run(); err == nil { |
||||
|
m.log.Debugf("Process %s terminated gracefully", pid) |
||||
|
time.Sleep(2 * time.Second) |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// Force kill if still running
|
||||
|
if err := exec.CommandContext(ctx, "kill", "-9", pid).Run(); err != nil { |
||||
|
return fmt.Errorf("failed to force kill process %s: %w", pid, err) |
||||
|
} |
||||
|
|
||||
|
m.log.Debugf("Process %s force killed", pid) |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (m *Manager) listCursorProcesses() ([]string, error) { |
||||
|
cmd := exec.Command("ps", "aux") |
||||
|
output, err := cmd.Output() |
||||
|
if err != nil { |
||||
|
return nil, fmt.Errorf("failed to execute ps command: %w", err) |
||||
|
} |
||||
|
|
||||
|
var pids []string |
||||
|
for _, line := range strings.Split(string(output), "\n") { |
||||
|
if strings.Contains(strings.ToLower(line), "apprun") { |
||||
|
fields := strings.Fields(line) |
||||
|
if len(fields) > 1 { |
||||
|
pids = append(pids, fields[1]) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return pids, nil |
||||
|
} |
@ -0,0 +1,101 @@ |
|||||
|
package ui |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"os" |
||||
|
"os/exec" |
||||
|
"runtime" |
||||
|
"strings" |
||||
|
|
||||
|
"github.com/fatih/color" |
||||
|
) |
||||
|
|
||||
|
// Display handles UI display operations
|
||||
|
type Display struct { |
||||
|
spinner *Spinner |
||||
|
} |
||||
|
|
||||
|
// NewDisplay creates a new display handler
|
||||
|
func NewDisplay(spinner *Spinner) *Display { |
||||
|
if spinner == nil { |
||||
|
spinner = NewSpinner(nil) |
||||
|
} |
||||
|
return &Display{ |
||||
|
spinner: spinner, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ShowProgress shows a progress message with spinner
|
||||
|
func (d *Display) ShowProgress(message string) { |
||||
|
d.spinner.SetMessage(message) |
||||
|
d.spinner.Start() |
||||
|
} |
||||
|
|
||||
|
// StopProgress stops the progress spinner
|
||||
|
func (d *Display) StopProgress() { |
||||
|
d.spinner.Stop() |
||||
|
} |
||||
|
|
||||
|
// ClearScreen clears the terminal screen
|
||||
|
func (d *Display) ClearScreen() error { |
||||
|
var cmd *exec.Cmd |
||||
|
if runtime.GOOS == "windows" { |
||||
|
cmd = exec.Command("cmd", "/c", "cls") |
||||
|
} else { |
||||
|
cmd = exec.Command("clear") |
||||
|
} |
||||
|
cmd.Stdout = os.Stdout |
||||
|
return cmd.Run() |
||||
|
} |
||||
|
|
||||
|
// ShowProcessStatus shows the current process status
|
||||
|
func (d *Display) ShowProcessStatus(message string) { |
||||
|
fmt.Printf("\r%s", strings.Repeat(" ", 80)) // Clear line
|
||||
|
fmt.Printf("\r%s", color.CyanString("⚡ "+message)) |
||||
|
} |
||||
|
|
||||
|
// ShowPrivilegeError shows the privilege error message
|
||||
|
func (d *Display) ShowPrivilegeError(errorMsg, adminMsg, sudoMsg, sudoExample string) { |
||||
|
red := color.New(color.FgRed, color.Bold) |
||||
|
yellow := color.New(color.FgYellow) |
||||
|
|
||||
|
red.Println(errorMsg) |
||||
|
if runtime.GOOS == "windows" { |
||||
|
yellow.Println(adminMsg) |
||||
|
} else { |
||||
|
yellow.Printf("%s\n%s\n", sudoMsg, fmt.Sprintf(sudoExample, os.Args[0])) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ShowSuccess shows a success message
|
||||
|
func (d *Display) ShowSuccess(successMsg, restartMsg string) { |
||||
|
green := color.New(color.FgGreen, color.Bold) |
||||
|
yellow := color.New(color.FgYellow, color.Bold) |
||||
|
|
||||
|
green.Printf("\n%s\n", successMsg) |
||||
|
yellow.Printf("%s\n", restartMsg) |
||||
|
} |
||||
|
|
||||
|
// ShowError shows an error message
|
||||
|
func (d *Display) ShowError(message string) { |
||||
|
red := color.New(color.FgRed, color.Bold) |
||||
|
red.Printf("\n%s\n", message) |
||||
|
} |
||||
|
|
||||
|
// ShowWarning shows a warning message
|
||||
|
func (d *Display) ShowWarning(message string) { |
||||
|
yellow := color.New(color.FgYellow, color.Bold) |
||||
|
yellow.Printf("\n%s\n", message) |
||||
|
} |
||||
|
|
||||
|
// ShowInfo shows an info message
|
||||
|
func (d *Display) ShowInfo(message string) { |
||||
|
cyan := color.New(color.FgCyan) |
||||
|
cyan.Printf("\n%s\n", message) |
||||
|
} |
||||
|
|
||||
|
// ShowPrompt shows a prompt message and waits for user input
|
||||
|
func (d *Display) ShowPrompt(message string) { |
||||
|
fmt.Print(message) |
||||
|
os.Stdout.Sync() |
||||
|
} |
@ -0,0 +1,110 @@ |
|||||
|
package ui |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"sync" |
||||
|
"time" |
||||
|
|
||||
|
"github.com/fatih/color" |
||||
|
) |
||||
|
|
||||
|
// SpinnerConfig defines spinner configuration
|
||||
|
type SpinnerConfig struct { |
||||
|
Frames []string |
||||
|
Delay time.Duration |
||||
|
} |
||||
|
|
||||
|
// DefaultSpinnerConfig returns the default spinner configuration
|
||||
|
func DefaultSpinnerConfig() *SpinnerConfig { |
||||
|
return &SpinnerConfig{ |
||||
|
Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, |
||||
|
Delay: 100 * time.Millisecond, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Spinner represents a progress spinner
|
||||
|
type Spinner struct { |
||||
|
config *SpinnerConfig |
||||
|
message string |
||||
|
current int |
||||
|
active bool |
||||
|
stopCh chan struct{} |
||||
|
mu sync.RWMutex |
||||
|
} |
||||
|
|
||||
|
// NewSpinner creates a new spinner with the given configuration
|
||||
|
func NewSpinner(config *SpinnerConfig) *Spinner { |
||||
|
if config == nil { |
||||
|
config = DefaultSpinnerConfig() |
||||
|
} |
||||
|
return &Spinner{ |
||||
|
config: config, |
||||
|
stopCh: make(chan struct{}), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// SetMessage sets the spinner message
|
||||
|
func (s *Spinner) SetMessage(message string) { |
||||
|
s.mu.Lock() |
||||
|
defer s.mu.Unlock() |
||||
|
s.message = message |
||||
|
} |
||||
|
|
||||
|
// Start starts the spinner animation
|
||||
|
func (s *Spinner) Start() { |
||||
|
s.mu.Lock() |
||||
|
if s.active { |
||||
|
s.mu.Unlock() |
||||
|
return |
||||
|
} |
||||
|
s.active = true |
||||
|
s.mu.Unlock() |
||||
|
|
||||
|
go s.run() |
||||
|
} |
||||
|
|
||||
|
// Stop stops the spinner animation
|
||||
|
func (s *Spinner) Stop() { |
||||
|
s.mu.Lock() |
||||
|
defer s.mu.Unlock() |
||||
|
|
||||
|
if !s.active { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
s.active = false |
||||
|
close(s.stopCh) |
||||
|
s.stopCh = make(chan struct{}) |
||||
|
fmt.Println() |
||||
|
} |
||||
|
|
||||
|
// IsActive returns whether the spinner is currently active
|
||||
|
func (s *Spinner) IsActive() bool { |
||||
|
s.mu.RLock() |
||||
|
defer s.mu.RUnlock() |
||||
|
return s.active |
||||
|
} |
||||
|
|
||||
|
func (s *Spinner) run() { |
||||
|
ticker := time.NewTicker(s.config.Delay) |
||||
|
defer ticker.Stop() |
||||
|
|
||||
|
for { |
||||
|
select { |
||||
|
case <-s.stopCh: |
||||
|
return |
||||
|
case <-ticker.C: |
||||
|
s.mu.RLock() |
||||
|
if !s.active { |
||||
|
s.mu.RUnlock() |
||||
|
return |
||||
|
} |
||||
|
frame := s.config.Frames[s.current%len(s.config.Frames)] |
||||
|
message := s.message |
||||
|
s.current++ |
||||
|
s.mu.RUnlock() |
||||
|
|
||||
|
fmt.Printf("\r%s %s", color.CyanString(frame), message) |
||||
|
} |
||||
|
} |
||||
|
} |
1050
main.go
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,95 @@ |
|||||
|
package idgen |
||||
|
|
||||
|
import ( |
||||
|
cryptorand "crypto/rand" |
||||
|
"crypto/sha256" |
||||
|
"encoding/hex" |
||||
|
"fmt" |
||||
|
"math/big" |
||||
|
"sync" |
||||
|
) |
||||
|
|
||||
|
// Generator handles the generation of various IDs
|
||||
|
type Generator struct { |
||||
|
charsetMu sync.RWMutex |
||||
|
charset string |
||||
|
} |
||||
|
|
||||
|
// NewGenerator creates a new ID generator with default settings
|
||||
|
func NewGenerator() *Generator { |
||||
|
return &Generator{ |
||||
|
charset: "0123456789ABCDEFGHJKLMNPQRSTVWXYZ", |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// SetCharset allows customizing the character set used for ID generation
|
||||
|
func (g *Generator) SetCharset(charset string) { |
||||
|
g.charsetMu.Lock() |
||||
|
defer g.charsetMu.Unlock() |
||||
|
g.charset = charset |
||||
|
} |
||||
|
|
||||
|
// GenerateMachineID generates a new machine ID with the format auth0|user_XX[unique_id]
|
||||
|
func (g *Generator) GenerateMachineID() (string, error) { |
||||
|
prefix := "auth0|user_" |
||||
|
|
||||
|
// Generate random sequence number between 0-99
|
||||
|
seqNum, err := cryptorand.Int(cryptorand.Reader, big.NewInt(100)) |
||||
|
if err != nil { |
||||
|
return "", fmt.Errorf("failed to generate sequence number: %w", err) |
||||
|
} |
||||
|
sequence := fmt.Sprintf("%02d", seqNum.Int64()) |
||||
|
|
||||
|
uniqueID, err := g.generateUniqueID(23) |
||||
|
if err != nil { |
||||
|
return "", fmt.Errorf("failed to generate unique ID: %w", err) |
||||
|
} |
||||
|
|
||||
|
fullID := prefix + sequence + uniqueID |
||||
|
return hex.EncodeToString([]byte(fullID)), nil |
||||
|
} |
||||
|
|
||||
|
// GenerateMacMachineID generates a new MAC machine ID using SHA-256
|
||||
|
func (g *Generator) GenerateMacMachineID() (string, error) { |
||||
|
data := make([]byte, 32) |
||||
|
if _, err := cryptorand.Read(data); err != nil { |
||||
|
return "", fmt.Errorf("failed to generate random data: %w", err) |
||||
|
} |
||||
|
|
||||
|
hash := sha256.Sum256(data) |
||||
|
return hex.EncodeToString(hash[:]), nil |
||||
|
} |
||||
|
|
||||
|
// GenerateDeviceID generates a new device ID in UUID v4 format
|
||||
|
func (g *Generator) GenerateDeviceID() (string, error) { |
||||
|
uuid := make([]byte, 16) |
||||
|
if _, err := cryptorand.Read(uuid); err != nil { |
||||
|
return "", fmt.Errorf("failed to generate UUID: %w", err) |
||||
|
} |
||||
|
|
||||
|
// Set version (4) and variant (2) bits according to RFC 4122
|
||||
|
uuid[6] = (uuid[6] & 0x0f) | 0x40 |
||||
|
uuid[8] = (uuid[8] & 0x3f) | 0x80 |
||||
|
|
||||
|
return fmt.Sprintf("%x-%x-%x-%x-%x", |
||||
|
uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:16]), nil |
||||
|
} |
||||
|
|
||||
|
// generateUniqueID generates a random string of specified length using the configured charset
|
||||
|
func (g *Generator) generateUniqueID(length int) (string, error) { |
||||
|
g.charsetMu.RLock() |
||||
|
defer g.charsetMu.RUnlock() |
||||
|
|
||||
|
result := make([]byte, length) |
||||
|
max := big.NewInt(int64(len(g.charset))) |
||||
|
|
||||
|
for i := range result { |
||||
|
randNum, err := cryptorand.Int(cryptorand.Reader, max) |
||||
|
if err != nil { |
||||
|
return "", fmt.Errorf("failed to generate random number: %w", err) |
||||
|
} |
||||
|
result[i] = g.charset[randNum.Int64()] |
||||
|
} |
||||
|
|
||||
|
return string(result), nil |
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
package idgen |
||||
|
|
||||
|
import ( |
||||
|
"testing" |
||||
|
|
||||
|
"github.com/stretchr/testify/assert" |
||||
|
) |
||||
|
|
||||
|
func TestNewGenerator(t *testing.T) { |
||||
|
gen := NewGenerator() |
||||
|
assert.NotNil(t, gen, "Generator should not be nil") |
||||
|
} |
||||
|
|
||||
|
func TestGenerateMachineID(t *testing.T) { |
||||
|
gen := NewGenerator() |
||||
|
id, err := gen.GenerateMachineID() |
||||
|
assert.NoError(t, err, "Should not return an error") |
||||
|
assert.NotEmpty(t, id, "Generated machine ID should not be empty") |
||||
|
} |
||||
|
|
||||
|
func TestGenerateDeviceID(t *testing.T) { |
||||
|
gen := NewGenerator() |
||||
|
id, err := gen.GenerateDeviceID() |
||||
|
assert.NoError(t, err, "Should not return an error") |
||||
|
assert.NotEmpty(t, id, "Generated device ID should not be empty") |
||||
|
} |
@ -1,128 +1,74 @@ |
|||||
@echo off |
@echo off |
||||
setlocal EnableDelayedExpansion |
setlocal EnableDelayedExpansion |
||||
|
|
||||
|
:: Build optimization flags |
||||
|
set "OPTIMIZATION_FLAGS=-trimpath -ldflags=\"-s -w\"" |
||||
|
set "BUILD_JOBS=4" |
||||
|
|
||||
:: Messages / 消息 |
:: Messages / 消息 |
||||
set "EN_MESSAGES[0]=Starting build process for version" |
set "EN_MESSAGES[0]=Starting build process for version" |
||||
set "EN_MESSAGES[1]=Using optimization flags:" |
set "EN_MESSAGES[1]=Using optimization flags:" |
||||
set "EN_MESSAGES[2]=Cleaning old builds..." |
set "EN_MESSAGES[2]=Cleaning old builds..." |
||||
set "EN_MESSAGES[3]=Cleanup completed" |
set "EN_MESSAGES[3]=Cleanup completed" |
||||
set "EN_MESSAGES[4]=bin directory does not exist, no cleanup needed" |
|
||||
set "EN_MESSAGES[5]=Starting builds for all platforms..." |
|
||||
set "EN_MESSAGES[6]=Building for" |
|
||||
set "EN_MESSAGES[7]=Build successful:" |
|
||||
set "EN_MESSAGES[8]=Build failed for" |
|
||||
set "EN_MESSAGES[9]=All builds completed! Total time:" |
|
||||
set "EN_MESSAGES[10]=seconds" |
|
||||
|
|
||||
set "CN_MESSAGES[0]=开始构建版本" |
|
||||
set "CN_MESSAGES[1]=使用优化标志:" |
|
||||
set "CN_MESSAGES[2]=正在清理旧的构建文件..." |
|
||||
set "CN_MESSAGES[3]=清理完成" |
|
||||
set "CN_MESSAGES[4]=bin 目录不存在,无需清理" |
|
||||
set "CN_MESSAGES[5]=开始编译所有平台..." |
|
||||
set "CN_MESSAGES[6]=正在构建" |
|
||||
set "CN_MESSAGES[7]=构建成功:" |
|
||||
set "CN_MESSAGES[8]=构建失败:" |
|
||||
set "CN_MESSAGES[9]=所有构建完成!总耗时:" |
|
||||
set "CN_MESSAGES[10]=秒" |
|
||||
|
set "EN_MESSAGES[4]=Starting builds for all platforms..." |
||||
|
set "EN_MESSAGES[5]=Building for" |
||||
|
set "EN_MESSAGES[6]=Build successful:" |
||||
|
set "EN_MESSAGES[7]=All builds completed!" |
||||
|
|
||||
:: 设置版本信息 / Set version |
|
||||
set VERSION=2.0.0 |
|
||||
|
|
||||
:: 设置颜色代码 / Set color codes |
|
||||
|
:: Colors |
||||
set "GREEN=[32m" |
set "GREEN=[32m" |
||||
set "RED=[31m" |
set "RED=[31m" |
||||
set "YELLOW=[33m" |
|
||||
set "RESET=[0m" |
set "RESET=[0m" |
||||
|
|
||||
:: 设置编译优化标志 / Set build optimization flags |
|
||||
set "LDFLAGS=-s -w" |
|
||||
set "BUILDMODE=pie" |
|
||||
set "GCFLAGS=-N -l" |
|
||||
|
|
||||
:: 设置 CGO / Set CGO |
|
||||
set CGO_ENABLED=0 |
|
||||
|
|
||||
:: 检测系统语言 / Detect system language |
|
||||
for /f "tokens=2 delims==" %%a in ('wmic os get OSLanguage /value') do set OSLanguage=%%a |
|
||||
if "%OSLanguage%"=="2052" (set LANG=cn) else (set LANG=en) |
|
||||
|
|
||||
:: 显示编译信息 / Display build info |
|
||||
echo %YELLOW%!%LANG%_MESSAGES[0]! %VERSION%%RESET% |
|
||||
echo %YELLOW%!%LANG%_MESSAGES[1]! LDFLAGS=%LDFLAGS%, BUILDMODE=%BUILDMODE%%RESET% |
|
||||
echo %YELLOW%CGO_ENABLED=%CGO_ENABLED%%RESET% |
|
||||
|
|
||||
:: 清理旧的构建文件 / Clean old builds |
|
||||
echo %YELLOW%!%LANG%_MESSAGES[2]!%RESET% |
|
||||
|
:: Cleanup function |
||||
|
:cleanup |
||||
if exist "..\bin" ( |
if exist "..\bin" ( |
||||
rd /s /q "..\bin" |
rd /s /q "..\bin" |
||||
echo %GREEN%!%LANG%_MESSAGES[3]!%RESET% |
|
||||
) else ( |
|
||||
echo %YELLOW%!%LANG%_MESSAGES[4]!%RESET% |
|
||||
|
echo %GREEN%!EN_MESSAGES[3]!%RESET% |
||||
) |
) |
||||
|
|
||||
:: 创建输出目录 / Create output directory |
|
||||
mkdir "..\bin" 2>nul |
mkdir "..\bin" 2>nul |
||||
|
|
||||
:: 定义目标平台数组 / Define target platforms array |
|
||||
set platforms[0].os=windows |
|
||||
set platforms[0].arch=amd64 |
|
||||
set platforms[0].ext=.exe |
|
||||
set platforms[0].suffix= |
|
||||
|
|
||||
set platforms[1].os=darwin |
|
||||
set platforms[1].arch=amd64 |
|
||||
set platforms[1].ext= |
|
||||
set platforms[1].suffix=_intel |
|
||||
|
|
||||
set platforms[2].os=darwin |
|
||||
set platforms[2].arch=arm64 |
|
||||
set platforms[2].ext= |
|
||||
set platforms[2].suffix=_m1 |
|
||||
|
|
||||
set platforms[3].os=linux |
|
||||
set platforms[3].arch=amd64 |
|
||||
set platforms[3].ext= |
|
||||
set platforms[3].suffix= |
|
||||
|
:: Build function with optimizations |
||||
|
:build |
||||
|
set "os=%~1" |
||||
|
set "arch=%~2" |
||||
|
set "ext=" |
||||
|
if "%os%"=="windows" set "ext=.exe" |
||||
|
|
||||
:: 设置开始时间 / Set start time |
|
||||
set start_time=%time% |
|
||||
|
echo %GREEN%!EN_MESSAGES[5]! %os%/%arch%%RESET% |
||||
|
|
||||
:: 编译所有目标 / Build all targets |
|
||||
echo !%LANG%_MESSAGES[5]! |
|
||||
|
set "CGO_ENABLED=0" |
||||
|
set "GOOS=%os%" |
||||
|
set "GOARCH=%arch%" |
||||
|
|
||||
for /L %%i in (0,1,3) do ( |
|
||||
set "os=!platforms[%%i].os!" |
|
||||
set "arch=!platforms[%%i].arch!" |
|
||||
set "ext=!platforms[%%i].ext!" |
|
||||
set "suffix=!platforms[%%i].suffix!" |
|
||||
|
start /b cmd /c "go build -trimpath -ldflags=\"-s -w\" -o ..\bin\%os%\%arch%\cursor-id-modifier%ext% -a -installsuffix cgo -mod=readonly ..\cmd\cursor-id-modifier" |
||||
|
exit /b 0 |
||||
|
|
||||
echo. |
|
||||
echo !%LANG%_MESSAGES[6]! !os! !arch!... |
|
||||
|
:: Main execution |
||||
|
echo %GREEN%!EN_MESSAGES[0]!%RESET% |
||||
|
echo %GREEN%!EN_MESSAGES[1]! %OPTIMIZATION_FLAGS%%RESET% |
||||
|
|
||||
set GOOS=!os! |
|
||||
set GOARCH=!arch! |
|
||||
|
call :cleanup |
||||
|
|
||||
:: 构建输出文件名 / Build output filename |
|
||||
set "outfile=..\bin\cursor_id_modifier_v%VERSION%_!os!_!arch!!suffix!!ext!" |
|
||||
|
echo %GREEN%!EN_MESSAGES[4]!%RESET% |
||||
|
|
||||
:: 执行构建 / Execute build |
|
||||
go build -trimpath -buildmode=%BUILDMODE% -ldflags="%LDFLAGS%" -gcflags="%GCFLAGS%" -o "!outfile!" ..\main.go |
|
||||
|
|
||||
if !errorlevel! equ 0 ( |
|
||||
echo %GREEN%!%LANG%_MESSAGES[7]! !outfile!%RESET% |
|
||||
) else ( |
|
||||
echo %RED%!%LANG%_MESSAGES[8]! !os! !arch!%RESET% |
|
||||
|
:: Start builds in parallel |
||||
|
set "pending=0" |
||||
|
for %%o in (windows linux darwin) do ( |
||||
|
for %%a in (amd64 386) do ( |
||||
|
call :build %%o %%a |
||||
|
set /a "pending+=1" |
||||
|
if !pending! geq %BUILD_JOBS% ( |
||||
|
timeout /t 1 /nobreak >nul |
||||
|
set "pending=0" |
||||
|
) |
||||
) |
) |
||||
) |
) |
||||
|
|
||||
:: 计算总耗时 / Calculate total time |
|
||||
set end_time=%time% |
|
||||
set /a duration = %end_time:~0,2% * 3600 + %end_time:~3,2% * 60 + %end_time:~6,2% - (%start_time:~0,2% * 3600 + %start_time:~3,2% * 60 + %start_time:~6,2%) |
|
||||
|
|
||||
echo. |
|
||||
echo %GREEN%!%LANG%_MESSAGES[9]! %duration% !%LANG%_MESSAGES[10]!%RESET% |
|
||||
if exist "..\bin" dir /b "..\bin" |
|
||||
|
:: Wait for all builds to complete |
||||
|
:wait_builds |
||||
|
timeout /t 2 /nobreak >nul |
||||
|
tasklist /fi "IMAGENAME eq go.exe" 2>nul | find "go.exe" >nul |
||||
|
if not errorlevel 1 goto wait_builds |
||||
|
|
||||
pause |
|
||||
endlocal |
|
||||
|
echo %GREEN%!EN_MESSAGES[7]!%RESET% |
Write
Preview
Loading…
Cancel
Save
Reference in new issue