#!/usr/bin/env python3 """ Skill 打包构建脚本(混淆模式) 使用方式: uv run --with pyarmor python build.py """ import os import sys import shutil import subprocess from datetime import datetime def generate_timestamp() -> str: """ 生成 YYYYMMDD_HHMMSS 格式的时间戳 Returns: 时间戳字符串 """ return datetime.now().strftime("%Y%m%d_%H%M%S") def get_git_config(key: str) -> str: """ 读取 git 配置项 Args: key: 配置项名称,如 "user.name" Returns: 配置值字符串 Raises: subprocess.CalledProcessError: git config 命令失败 """ result = subprocess.run( ["git", "config", "--get", key], capture_output=True, text=True, check=True ) return result.stdout.strip() def get_git_user_info() -> tuple[str, str]: """ 读取 git user.name 和 user.email Returns: (name, email) 元组 Raises: SystemExit: git 配置未设置时退出 """ try: name = get_git_config("user.name") except subprocess.CalledProcessError: print(""" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 错误: git user.name 未设置 请先配置 git 用户名: git config --global user.name "Your Name" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ """) sys.exit(1) try: email = get_git_config("user.email") except subprocess.CalledProcessError: print(""" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 错误: git user.email 未设置 请先配置 git 邮箱: git config --global user.email "your@email.com" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ """) sys.exit(1) return name, email def clean_and_create_build_dir(build_dir: str) -> None: """ 删除旧 build 目录并创建新的空目录 Args: build_dir: 构建目录路径 """ if os.path.exists(build_dir): print(f"清理旧构建目录: {build_dir}") shutil.rmtree(build_dir) os.makedirs(build_dir) print(f"创建构建目录: {build_dir}") def copy_skill_md(source_path: str, target_dir: str, version: str, author: str) -> None: """ 读取 SKILL.md 模板,动态注入 version 和 author 后写入 build/SKILL.md Args: source_path: 源 SKILL.md 路径 target_dir: 目标目录 version: 版本号 author: 作者信息 (格式: "Name ") """ target_path = os.path.join(target_dir, "SKILL.md") with open(source_path, "r", encoding="utf-8") as f: content = f.read() lines = content.split("\n") # 解析 frontmatter frontmatter_start = -1 frontmatter_end = -1 frontmatter_count = 0 has_metadata = False metadata_idx = -1 for i, line in enumerate(lines): stripped = line.rstrip() if stripped == "---": frontmatter_count += 1 if frontmatter_count == 1: frontmatter_start = i elif frontmatter_count == 2: frontmatter_end = i break elif frontmatter_count == 1 and stripped == "metadata:": has_metadata = True metadata_idx = i result_lines = [] if frontmatter_start >= 0 and frontmatter_end > frontmatter_start: # 有 frontmatter i = 0 while i < len(lines): if i < frontmatter_start or i >= frontmatter_end: result_lines.append(lines[i]) i += 1 else: # 在 frontmatter 内部 if has_metadata and i == metadata_idx: # 找到 metadata: 行 result_lines.append(lines[i]) i += 1 version_written = False author_written = False # 遍历 metadata 子项,替换 version/author,保留其他 while i < frontmatter_end: stripped_line = lines[i].rstrip() if stripped_line.startswith(" version:"): if not version_written: result_lines.append(f" version: \"{version}\"") version_written = True i += 1 elif stripped_line.startswith(" author:"): if not author_written: result_lines.append(f" author: \"{author}\"") author_written = True i += 1 elif stripped_line.startswith(" "): # 其他 metadata 子项,保留 result_lines.append(lines[i]) i += 1 else: # metadata 块结束 break # 确保 version/author 都写了 if not version_written: result_lines.append(f" version: \"{version}\"") if not author_written: result_lines.append(f" author: \"{author}\"") elif not has_metadata and i == frontmatter_end - 1: # 没有 metadata,在 frontmatter 末尾插入 result_lines.append("metadata:") result_lines.append(f" version: \"{version}\"") result_lines.append(f" author: \"{author}\"") result_lines.append(lines[i]) i += 1 else: result_lines.append(lines[i]) i += 1 else: # 没有 frontmatter,新建一个 result_lines.append("---") result_lines.append("name: lyxy-document-reader") result_lines.append("metadata:") result_lines.append(f" version: \"{version}\"") result_lines.append(f" author: \"{author}\"") result_lines.append("---") result_lines.append("") result_lines.extend(lines) new_content = "\n".join(result_lines) with open(target_path, "w", encoding="utf-8") as f: f.write(new_content) print(f"生成: {target_path} (version: {version}, author: {author})") def obfuscate_scripts_dir(source_dir: str, target_dir: str) -> None: """ 使用 PyArmor 混淆 scripts 目录 Args: source_dir: 源代码目录 (scripts/) target_dir: 目标构建目录 (build/) """ # 检查 pyarmor 是否可用 try: __import__("pyarmor") except ImportError: print(""" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 错误: PyArmor 未安装 请使用以下命令: uv run --with pyarmor python build.py ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ """) sys.exit(1) # 临时目录 temp_dir = os.path.join(target_dir, "temp_pyarmor") # 清理已存在的临时目录 if os.path.exists(temp_dir): shutil.rmtree(temp_dir) # PyArmor 命令 (Normal Mode) cmd = [ "pyarmor", "gen", "--recursive", "-O", temp_dir, source_dir ] print(f" 执行: {' '.join(cmd)}") try: result = subprocess.run( cmd, check=True, capture_output=True, text=True ) except subprocess.CalledProcessError as e: print(f"\nPyArmor 混淆失败:") print(f" 返回码: {e.returncode}") print(f" 标准输出: {e.stdout}") print(f" 错误输出: {e.stderr}") sys.exit(1) # 移动混淆后的文件到最终位置 for item in os.listdir(temp_dir): src = os.path.join(temp_dir, item) dst = os.path.join(target_dir, item) if os.path.exists(dst): if os.path.isdir(dst): shutil.rmtree(dst) else: os.remove(dst) shutil.move(src, dst) # 清理临时目录 os.rmdir(temp_dir) print(" 混淆完成") def main() -> None: """ 主函数:执行完整的混淆打包流程 """ print("=" * 60) print("Skill 打包构建 (混淆模式)") print("=" * 60) # 路径配置 project_root = os.path.dirname(os.path.abspath(__file__)) skill_md_path = os.path.join(project_root, "SKILL.md") scripts_source_dir = os.path.join(project_root, "scripts") build_dir = os.path.join(project_root, "build") # 生成版本号 version = generate_timestamp() print(f"版本号: {version}") # 读取 git 用户信息 git_name, git_email = get_git_user_info() author = f"{git_name} <{git_email}>" print(f"作者: {author}") print() # 清理并创建 build 目录 clean_and_create_build_dir(build_dir) print() # 复制 SKILL.md(动态注入元数据) copy_skill_md(skill_md_path, build_dir, version, author) print() # 混淆代码 print("────────────────────────────────────────") print(" 使用 PyArmor 混淆代码 (Normal Mode)") print("────────────────────────────────────────") obfuscate_scripts_dir(scripts_source_dir, build_dir) print() # 完成信息 print("=" * 60) print("构建完成!") print(f"版本号: {version}") print(f"作者: {author}") print("混淆模式: 已生成 .pyx 和 pyarmor_runtime") print(f"输出目录: {build_dir}") print("=" * 60) if __name__ == "__main__": main()