Files
macm1new/build.py
2025-02-19 16:29:59 +08:00

349 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()