feat: 添加 PyArmor 代码混淆支持

- 新增 --obfuscate 命令行参数,支持使用 PyArmor 混淆代码
- 通过 uv run --with pyarmor 按需加载 PyArmor,不污染主机环境
- 添加友好的错误提示,引导用户正确使用 --with pyarmor
- 保持非混淆模式完全向后兼容
- 更新 skill-packaging spec,新增混淆相关需求
This commit is contained in:
2026-03-09 14:36:52 +08:00
parent c140bda66b
commit d860e17b2c
2 changed files with 166 additions and 6 deletions

125
build.py
View File

@@ -1,11 +1,20 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Skill 打包构建脚本 Skill 打包构建脚本
将 skill/SKILL.md 和 scripts/ 目录打包到 build/ 目录
使用方式:
# 开发模式 - 快速构建,不混淆
uv run python build.py
# 发布模式 - 完整构建PyArmor 混淆
uv run --with pyarmor python build.py --obfuscate
""" """
import os import os
import sys
import shutil import shutil
import subprocess
import argparse
from datetime import datetime from datetime import datetime
@@ -88,10 +97,104 @@ def copy_scripts_dir(source_dir: str, target_dir: str) -> int:
return file_count 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: 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("=" * 60)
print("Skill 打包构建") print("Skill 打包构建")
print("=" * 60) print("=" * 60)
@@ -101,7 +204,6 @@ def main() -> None:
skill_md_path = os.path.join(project_root, "SKILL.md") skill_md_path = os.path.join(project_root, "SKILL.md")
scripts_source_dir = os.path.join(project_root, "scripts") scripts_source_dir = os.path.join(project_root, "scripts")
build_dir = os.path.join(project_root, "build") build_dir = os.path.join(project_root, "build")
scripts_target_dir = os.path.join(build_dir, "scripts")
# 生成时间戳 # 生成时间戳
version = generate_timestamp() version = generate_timestamp()
@@ -116,16 +218,27 @@ def main() -> None:
copy_skill_md(skill_md_path, build_dir) copy_skill_md(skill_md_path, build_dir)
print() print()
# 复制 scripts 目录 # 根据 --obfuscate 选择执行路径
print("复制 scripts/ 目录(仅 .py 文件):") if args.obfuscate:
file_count = copy_scripts_dir(scripts_source_dir, scripts_target_dir) 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()
# 完成信息 # 完成信息
print("=" * 60) print("=" * 60)
print("构建完成!") print("构建完成!")
print(f"版本号: {version}") 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(f"输出目录: {build_dir}")
print("=" * 60) print("=" * 60)

View File

@@ -52,3 +52,50 @@
#### Scenario: 显示构建信息 #### Scenario: 显示构建信息
- **WHEN** 构建成功完成 - **WHEN** 构建成功完成
- **THEN** 控制台输出版本号和构建文件清单 - **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** 系统显示退出码、标准输出和错误输出信息