feat: 添加 PyArmor 代码混淆支持
- 新增 --obfuscate 命令行参数,支持使用 PyArmor 混淆代码 - 通过 uv run --with pyarmor 按需加载 PyArmor,不污染主机环境 - 添加友好的错误提示,引导用户正确使用 --with pyarmor - 保持非混淆模式完全向后兼容 - 更新 skill-packaging spec,新增混淆相关需求
This commit is contained in:
125
build.py
125
build.py
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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** 系统显示退出码、标准输出和错误输出信息
|
||||||
|
|||||||
Reference in New Issue
Block a user