From d860e17b2c7273dad91409655d46116eeae55475 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Mon, 9 Mar 2026 14:36:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20PyArmor=20?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=B7=B7=E6=B7=86=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 --obfuscate 命令行参数,支持使用 PyArmor 混淆代码 - 通过 uv run --with pyarmor 按需加载 PyArmor,不污染主机环境 - 添加友好的错误提示,引导用户正确使用 --with pyarmor - 保持非混淆模式完全向后兼容 - 更新 skill-packaging spec,新增混淆相关需求 --- build.py | 125 +++++++++++++++++++++++-- openspec/specs/skill-packaging/spec.md | 47 ++++++++++ 2 files changed, 166 insertions(+), 6 deletions(-) diff --git a/build.py b/build.py index 3c25971..cc19206 100644 --- a/build.py +++ b/build.py @@ -1,11 +1,20 @@ #!/usr/bin/env python3 """ Skill 打包构建脚本 -将 skill/SKILL.md 和 scripts/ 目录打包到 build/ 目录 + +使用方式: + # 开发模式 - 快速构建,不混淆 + uv run python build.py + + # 发布模式 - 完整构建,PyArmor 混淆 + uv run --with pyarmor python build.py --obfuscate """ import os +import sys import shutil +import subprocess +import argparse from datetime import datetime @@ -88,10 +97,104 @@ def copy_scripts_dir(source_dir: str, target_dir: str) -> int: return file_count +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 --obfuscate + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + """) + 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: """ 主函数:执行完整的打包流程 """ + parser = argparse.ArgumentParser( + description="Skill 打包构建", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +使用示例: + # 开发模式 - 快速构建,不混淆 + uv run python build.py + + # 发布模式 - 完整构建,PyArmor 混淆 + uv run --with pyarmor python build.py --obfuscate + """ + ) + parser.add_argument( + "--obfuscate", + action="store_true", + help="使用 PyArmor 混淆代码 (需: uv run --with pyarmor)" + ) + args = parser.parse_args() + print("=" * 60) print("Skill 打包构建") print("=" * 60) @@ -101,7 +204,6 @@ def main() -> None: 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") - scripts_target_dir = os.path.join(build_dir, "scripts") # 生成时间戳 version = generate_timestamp() @@ -116,16 +218,27 @@ def main() -> None: copy_skill_md(skill_md_path, build_dir) print() - # 复制 scripts 目录 - print("复制 scripts/ 目录(仅 .py 文件):") - file_count = copy_scripts_dir(scripts_source_dir, scripts_target_dir) + # 根据 --obfuscate 选择执行路径 + if args.obfuscate: + print("────────────────────────────────────────") + print(" 使用 PyArmor 混淆代码 (Normal Mode)") + print("────────────────────────────────────────") + obfuscate_scripts_dir(scripts_source_dir, build_dir) + file_count = None + else: + scripts_target_dir = os.path.join(build_dir, "scripts") + print("复制 scripts/ 目录(仅 .py 文件):") + file_count = copy_scripts_dir(scripts_source_dir, scripts_target_dir) print() # 完成信息 print("=" * 60) print("构建完成!") print(f"版本号: {version}") - print(f"复制文件数: {file_count}") + if file_count is not None: + print(f"复制文件数: {file_count}") + else: + print("混淆模式: 已生成 .pyx 和 pyarmor_runtime") print(f"输出目录: {build_dir}") print("=" * 60) diff --git a/openspec/specs/skill-packaging/spec.md b/openspec/specs/skill-packaging/spec.md index 0d73863..1c914c8 100644 --- a/openspec/specs/skill-packaging/spec.md +++ b/openspec/specs/skill-packaging/spec.md @@ -52,3 +52,50 @@ #### Scenario: 显示构建信息 - **WHEN** 构建成功完成 - **THEN** 控制台输出版本号和构建文件清单 + +### Requirement: --obfuscate 参数支持 +系统 SHALL 支持 `--obfuscate` 命令行参数,用于启用代码混淆功能。 + +#### Scenario: 使用 --obfuscate 参数 +- **WHEN** 用户执行 `uv run --with pyarmor python build.py --obfuscate` +- **THEN** 系统使用 PyArmor 对 scripts 目录代码进行混淆 + +#### Scenario: 不使用 --obfuscate 参数 +- **WHEN** 用户执行 `uv run python build.py`(不带 --obfuscate) +- **THEN** 系统执行原有的复制行为,不进行混淆 + +### Requirement: PyArmor 混淆执行 +系统 SHALL 在 `--obfuscate` 模式下调用 PyArmor 工具对 scripts 目录进行混淆。 + +#### Scenario: PyArmor 成功执行 +- **WHEN** 启用 --obfuscate 且 PyArmor 可用 +- **THEN** 系统执行 pyarmor gen --recursive 命令 + +#### Scenario: 混淆后文件输出 +- **WHEN** PyArmor 混淆完成 +- **THEN** build/scripts/ 目录包含混淆后的文件 + +#### Scenario: pyarmor_runtime 包含 +- **WHEN** PyArmor 混淆完成 +- **THEN** build/scripts/ 目录包含 pyarmor_runtime_xxxxxx 子目录 + +### Requirement: PyArmor 未安装友好提示 +系统 SHALL 在 PyArmor 未安装时提供清晰的错误提示,引导用户正确使用 `uv run --with pyarmor`。 + +#### Scenario: PyArmor ImportError +- **WHEN** 启用 --obfuscate 但未通过 --with pyarmor 加载 +- **THEN** 系统显示友好错误信息,提示正确命令 + +### Requirement: SKILL.md 保持明文 +系统 SHALL 在混淆模式下仍然将 SKILL.md 作为明文文件复制,不进行混淆。 + +#### Scenario: SKILL.md 保持明文 +- **WHEN** 启用 --obfuscate 执行构建 +- **THEN** build/SKILL.md 文件为明文,内容与原文件一致 + +### Requirement: 混淆错误处理 +系统 SHALL 在 PyArmor 混淆失败时捕获错误并显示详细信息。 + +#### Scenario: PyArmor 命令失败 +- **WHEN** pyarmor 命令执行返回非零退出码 +- **THEN** 系统显示退出码、标准输出和错误输出信息