349 lines
10 KiB
Python
349 lines
10 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import warnings
|
||
import os
|
||
import platform
|
||
import subprocess
|
||
import time
|
||
import threading
|
||
import json
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
# Ignore specific SyntaxWarning
|
||
warnings.filterwarnings("ignore", category=SyntaxWarning, module="DrissionPage")
|
||
|
||
CURSOR_LOGO = """
|
||
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
|
||
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
|
||
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
|
||
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
|
||
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
|
||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
|
||
"""
|
||
|
||
|
||
class LoadingAnimation:
|
||
def __init__(self):
|
||
self.is_running = False
|
||
self.animation_thread = None
|
||
|
||
def start(self, message="Building"):
|
||
self.is_running = True
|
||
self.animation_thread = threading.Thread(target=self._animate, args=(message,))
|
||
self.animation_thread.start()
|
||
|
||
def stop(self):
|
||
self.is_running = False
|
||
if self.animation_thread:
|
||
self.animation_thread.join()
|
||
print("\r" + " " * 70 + "\r", end="", flush=True) # Clear the line
|
||
|
||
def _animate(self, message):
|
||
animation = "|/-\\"
|
||
idx = 0
|
||
while self.is_running:
|
||
print(f"\r{message} {animation[idx % len(animation)]}", end="", flush=True)
|
||
idx += 1
|
||
time.sleep(0.1)
|
||
|
||
|
||
def print_logo():
|
||
print("\033[96m" + CURSOR_LOGO + "\033[0m")
|
||
print("\033[93m" + "Building Cursor Keep Alive...".center(56) + "\033[0m\n")
|
||
|
||
|
||
def progress_bar(progress, total, prefix="", length=50):
|
||
filled = int(length * progress // total)
|
||
bar = "█" * filled + "░" * (length - filled)
|
||
percent = f"{100 * progress / total:.1f}"
|
||
print(f"\r{prefix} |{bar}| {percent}% Complete", end="", flush=True)
|
||
if progress == total:
|
||
print()
|
||
|
||
|
||
def simulate_progress(message, duration=1.0, steps=20):
|
||
print(f"\033[94m{message}\033[0m")
|
||
for i in range(steps + 1):
|
||
time.sleep(duration / steps)
|
||
progress_bar(i, steps, prefix="Progress:", length=40)
|
||
|
||
|
||
def filter_output(output):
|
||
"""ImportantMessage"""
|
||
if not output:
|
||
return ""
|
||
important_lines = []
|
||
for line in output.split("\n"):
|
||
# Only keep lines containing specific keywords
|
||
if any(
|
||
keyword in line.lower()
|
||
for keyword in ["error:", "failed:", "completed", "directory:"]
|
||
):
|
||
important_lines.append(line)
|
||
return "\n".join(important_lines)
|
||
|
||
|
||
def increment_version():
|
||
"""增加构建版本号"""
|
||
version_file = Path("version.json")
|
||
if version_file.exists():
|
||
with open(version_file, "r") as f:
|
||
version_data = json.load(f)
|
||
|
||
# 增加构建号
|
||
version_data["build"] += 1
|
||
|
||
# 更新版本号的最后一位
|
||
version_parts = version_data["version"].split(".")
|
||
version_parts[-1] = str(version_data["build"])
|
||
version_data["version"] = ".".join(version_parts)
|
||
|
||
# 保存更新后的版本信息
|
||
with open(version_file, "w") as f:
|
||
json.dump(version_data, f, indent=4)
|
||
|
||
return version_data
|
||
else:
|
||
print("错误:未找到 version.json 文件")
|
||
sys.exit(1)
|
||
|
||
|
||
def create_icns():
|
||
"""将 SVG 转换为 ICNS 格式"""
|
||
if not Path("icons/logo.svg").exists():
|
||
print("错误:未找到 logo.svg 文件")
|
||
sys.exit(1)
|
||
|
||
# 创建临时目录
|
||
os.makedirs("icons/tmp.iconset", exist_ok=True)
|
||
|
||
# 转换 SVG 到 PNG
|
||
sizes = [16, 32, 64, 128, 256, 512, 1024]
|
||
for size in sizes:
|
||
# 普通分辨率
|
||
os.system(f"rsvg-convert -w {size} -h {size} icons/logo.svg > icons/tmp.iconset/icon_{size}x{size}.png")
|
||
# 高分辨率(@2x)
|
||
if size <= 512:
|
||
os.system(f"rsvg-convert -w {size*2} -h {size*2} icons/logo.svg > icons/tmp.iconset/icon_{size}x{size}@2x.png")
|
||
|
||
# 生成 icns 文件
|
||
os.system("iconutil -c icns icons/tmp.iconset -o icons/logo.icns")
|
||
|
||
# 清理临时文件
|
||
os.system("rm -rf icons/tmp.iconset")
|
||
|
||
|
||
def update_spec(version_data):
|
||
"""更新 spec 文件中的版本信息"""
|
||
spec_content = f'''# -*- mode: python ; coding: utf-8 -*-
|
||
|
||
block_cipher = None
|
||
|
||
a = Analysis(
|
||
['cursor_gui.py'],
|
||
pathex=[],
|
||
binaries=[],
|
||
datas=[],
|
||
hiddenimports=[],
|
||
hookspath=[],
|
||
hooksconfig={{}},
|
||
runtime_hooks=[],
|
||
excludes=[],
|
||
win_no_prefer_redirects=False,
|
||
win_private_assemblies=False,
|
||
cipher=block_cipher,
|
||
noarchive=False,
|
||
)
|
||
|
||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||
|
||
exe = EXE(
|
||
pyz,
|
||
a.scripts,
|
||
[],
|
||
exclude_binaries=True,
|
||
name='听泉Cursor助手',
|
||
debug=False,
|
||
bootloader_ignore_signals=False,
|
||
strip=False,
|
||
upx=True,
|
||
console=False,
|
||
disable_windowed_traceback=False,
|
||
argv_emulation=True,
|
||
target_arch=None,
|
||
codesign_identity=None,
|
||
entitlements_file=None,
|
||
icon='icons/logo.icns',
|
||
)
|
||
|
||
coll = COLLECT(
|
||
exe,
|
||
a.binaries,
|
||
a.zipfiles,
|
||
a.datas,
|
||
strip=False,
|
||
upx=True,
|
||
upx_exclude=[],
|
||
name='听泉Cursor助手',
|
||
)
|
||
|
||
app = BUNDLE(
|
||
coll,
|
||
name='听泉Cursor助手.app',
|
||
icon='icons/logo.icns',
|
||
bundle_identifier='com.tingquan.cursor',
|
||
info_plist={{
|
||
'CFBundleShortVersionString': '{version_data["version"]}',
|
||
'CFBundleVersion': '{version_data["build"]}',
|
||
'NSHighResolutionCapable': 'True',
|
||
'LSMinimumSystemVersion': '10.13.0',
|
||
'CFBundleName': '听泉Cursor助手',
|
||
'CFBundleDisplayName': '听泉Cursor助手',
|
||
}},
|
||
)'''
|
||
|
||
with open("cursor_app.spec", "w") as f:
|
||
f.write(spec_content)
|
||
|
||
|
||
def build_app():
|
||
"""构建应用程序"""
|
||
# 增加版本号
|
||
version_data = increment_version()
|
||
|
||
# 创建图标
|
||
create_icns()
|
||
|
||
# 更新 spec 文件
|
||
update_spec(version_data)
|
||
|
||
# 更新主程序中的版本号
|
||
update_main_version(version_data["version"])
|
||
|
||
# 构建应用
|
||
os.system("pyinstaller cursor_app.spec")
|
||
|
||
# 创建压缩包
|
||
os.system(f'cd dist && zip -r "听泉Cursor助手_v{version_data["version"]}.zip" "听泉Cursor助手.app"')
|
||
|
||
print(f"\n构建完成!版本号:v{version_data['version']}")
|
||
print(f"应用程序位置:dist/听泉Cursor助手.app")
|
||
print(f"压缩包位置:dist/听泉Cursor助手_v{version_data['version']}.zip")
|
||
|
||
|
||
def update_main_version(version):
|
||
"""更新主程序中的版本号"""
|
||
with open("cursor_gui.py", "r") as f:
|
||
content = f.read()
|
||
|
||
# 替换版本号
|
||
content = content.replace(
|
||
'self.setWindowTitle("Cursor账号管理器 v3.5.3")',
|
||
f'self.setWindowTitle("听泉Cursor助手 v{version}")'
|
||
)
|
||
|
||
with open("cursor_gui.py", "w") as f:
|
||
f.write(content)
|
||
|
||
|
||
def build():
|
||
# Clear screen
|
||
os.system("cls" if platform.system().lower() == "windows" else "clear")
|
||
|
||
# Print logo
|
||
print_logo()
|
||
|
||
system = platform.system().lower()
|
||
spec_file = os.path.join("CursorKeepAlive.spec")
|
||
|
||
# if system not in ["darwin", "windows"]:
|
||
# print(f"\033[91mUnsupported operating system: {system}\033[0m")
|
||
# return
|
||
|
||
output_dir = f"dist/{system if system != 'darwin' else 'mac'}"
|
||
|
||
# Create output directory
|
||
os.makedirs(output_dir, exist_ok=True)
|
||
simulate_progress("Creating output directory...", 0.5)
|
||
|
||
# Run PyInstaller with loading animation
|
||
pyinstaller_command = [
|
||
"pyinstaller",
|
||
spec_file,
|
||
"--distpath",
|
||
output_dir,
|
||
"--workpath",
|
||
f"build/{system}",
|
||
"--noconfirm",
|
||
]
|
||
|
||
loading = LoadingAnimation()
|
||
try:
|
||
simulate_progress("Running PyInstaller...", 2.0)
|
||
loading.start("Building in progress")
|
||
result = subprocess.run(
|
||
pyinstaller_command, check=True, capture_output=True, text=True
|
||
)
|
||
loading.stop()
|
||
|
||
if result.stderr:
|
||
filtered_errors = [
|
||
line
|
||
for line in result.stderr.split("\n")
|
||
if any(
|
||
keyword in line.lower()
|
||
for keyword in ["error:", "failed:", "completed", "directory:"]
|
||
)
|
||
]
|
||
if filtered_errors:
|
||
print("\033[93mBuild Warnings/Errors:\033[0m")
|
||
print("\n".join(filtered_errors))
|
||
|
||
except subprocess.CalledProcessError as e:
|
||
loading.stop()
|
||
print(f"\033[91mBuild failed with error code {e.returncode}\033[0m")
|
||
if e.stderr:
|
||
print("\033[91mError Details:\033[0m")
|
||
print(e.stderr)
|
||
return
|
||
except FileNotFoundError:
|
||
loading.stop()
|
||
print(
|
||
"\033[91mError: Please ensure PyInstaller is installed (pip install pyinstaller)\033[0m"
|
||
)
|
||
return
|
||
except KeyboardInterrupt:
|
||
loading.stop()
|
||
print("\n\033[91mBuild cancelled by user\033[0m")
|
||
return
|
||
finally:
|
||
loading.stop()
|
||
|
||
# Copy config file
|
||
if os.path.exists("config.ini.example"):
|
||
simulate_progress("Copying configuration file...", 0.5)
|
||
if system == "windows":
|
||
subprocess.run(
|
||
["copy", "config.ini.example", f"{output_dir}\\config.ini"], shell=True
|
||
)
|
||
else:
|
||
subprocess.run(["cp", "config.ini.example", f"{output_dir}/config.ini"])
|
||
|
||
# Copy .env.example file
|
||
if os.path.exists(".env.example"):
|
||
simulate_progress("Copying environment file...", 0.5)
|
||
if system == "windows":
|
||
subprocess.run(["copy", ".env.example", f"{output_dir}\\.env"], shell=True)
|
||
else:
|
||
subprocess.run(["cp", ".env.example", f"{output_dir}/.env"])
|
||
|
||
print(
|
||
f"\n\033[92mBuild completed successfully! Output directory: {output_dir}\033[0m"
|
||
)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
build_app()
|