292 lines
10 KiB
Python
292 lines
10 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
解析版本头文件并打包二进制文件的脚本
|
||
|
|
用法: python package_binaries.py <version_header_file>
|
||
|
|
"""
|
||
|
|
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import re
|
||
|
|
import zipfile
|
||
|
|
import tarfile
|
||
|
|
import platform
|
||
|
|
|
||
|
|
def parse_version_header(file_path):
|
||
|
|
"""
|
||
|
|
解析版本头文件,提取路径信息
|
||
|
|
返回字典包含解析结果
|
||
|
|
"""
|
||
|
|
if not os.path.exists(file_path):
|
||
|
|
raise FileNotFoundError(f"文件不存在: {file_path}")
|
||
|
|
|
||
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||
|
|
content = f.read()
|
||
|
|
|
||
|
|
# 使用正则表达式提取信息
|
||
|
|
patterns = {
|
||
|
|
'PROJECT_NAME': r'#define\s+PROJECT_NAME\s+"([^"]+)"',
|
||
|
|
'VERSION_GIT_COMMIT': r'#define\s+VERSION_GIT_COMMIT\s+"([^"]+)"',
|
||
|
|
'VERSION_GIT_BRANCH': r'#define\s+VERSION_GIT_BRANCH\s+"([^"]+)"',
|
||
|
|
'CMAKE_RUNTIME_OUTPUT_DIRECTORY': r'#define\s+CMAKE_RUNTIME_OUTPUT_DIRECTORY\s+"([^"]+)"',
|
||
|
|
'LIBRARY_OUTPUT_PATH': r'#define\s+LIBRARY_OUTPUT_PATH\s+"([^"]+)"',
|
||
|
|
}
|
||
|
|
|
||
|
|
result = {}
|
||
|
|
for key, pattern in patterns.items():
|
||
|
|
match = re.search(pattern, content)
|
||
|
|
if match:
|
||
|
|
result[key] = match.group(1)
|
||
|
|
else:
|
||
|
|
# 尝试无引号的情况
|
||
|
|
pattern_no_quotes = pattern.replace(r'"([^"]+)"', r'(\S+)')
|
||
|
|
match = re.search(pattern_no_quotes, content)
|
||
|
|
if match:
|
||
|
|
result[key] = match.group(1)
|
||
|
|
else:
|
||
|
|
print(f"警告: 未找到 {key} 定义")
|
||
|
|
result[key] = ""
|
||
|
|
|
||
|
|
# 验证必要字段
|
||
|
|
required_fields = ['PROJECT_NAME', 'VERSION_GIT_COMMIT', 'VERSION_GIT_BRANCH']
|
||
|
|
for field in required_fields:
|
||
|
|
if not result.get(field):
|
||
|
|
raise ValueError(f"缺少必要字段: {field}")
|
||
|
|
|
||
|
|
return result
|
||
|
|
|
||
|
|
def normalize_path(path_str):
|
||
|
|
"""标准化路径,处理Windows/Unix路径差异"""
|
||
|
|
if not path_str:
|
||
|
|
return ""
|
||
|
|
|
||
|
|
# 替换反斜杠为正斜杠
|
||
|
|
path_str = path_str.replace('\\', '/')
|
||
|
|
|
||
|
|
# 移除可能的尾部斜杠
|
||
|
|
path_str = path_str.rstrip('/')
|
||
|
|
|
||
|
|
return path_str
|
||
|
|
|
||
|
|
def collect_binary_files(runtime_dir, library_dir):
|
||
|
|
"""
|
||
|
|
收集指定目录下的二进制文件
|
||
|
|
返回文件路径列表
|
||
|
|
"""
|
||
|
|
binary_files = []
|
||
|
|
dirs_to_search = []
|
||
|
|
|
||
|
|
if runtime_dir and os.path.exists(runtime_dir):
|
||
|
|
dirs_to_search.append(runtime_dir)
|
||
|
|
print(f"搜索运行时目录: {runtime_dir}")
|
||
|
|
|
||
|
|
if library_dir and os.path.exists(library_dir):
|
||
|
|
dirs_to_search.append(library_dir)
|
||
|
|
print(f"搜索库目录: {library_dir}")
|
||
|
|
|
||
|
|
# 支持的二进制文件扩展名
|
||
|
|
binary_extensions = {
|
||
|
|
'windows': ['.exe', '.dll', '.pdb', '.lib'],
|
||
|
|
'linux': ['', '.so', '.so.*', '.a'],
|
||
|
|
'darwin': ['', '.dylib', '.a', '.bundle']
|
||
|
|
}
|
||
|
|
|
||
|
|
current_os = platform.system().lower()
|
||
|
|
if 'windows' in current_os:
|
||
|
|
extensions = binary_extensions['windows']
|
||
|
|
elif 'linux' in current_os:
|
||
|
|
extensions = binary_extensions['linux']
|
||
|
|
elif 'darwin' in current_os:
|
||
|
|
extensions = binary_extensions['darwin']
|
||
|
|
else:
|
||
|
|
extensions = binary_extensions['linux'] # 默认
|
||
|
|
|
||
|
|
for search_dir in dirs_to_search:
|
||
|
|
if not os.path.exists(search_dir):
|
||
|
|
print(f"警告: 目录不存在: {search_dir}")
|
||
|
|
continue
|
||
|
|
|
||
|
|
for root, dirs, files in os.walk(search_dir):
|
||
|
|
for file in files:
|
||
|
|
file_path = os.path.join(root, file)
|
||
|
|
|
||
|
|
# 检查文件扩展名
|
||
|
|
should_include = False
|
||
|
|
for ext in extensions:
|
||
|
|
if ext.endswith('*'):
|
||
|
|
# 通配符匹配
|
||
|
|
if file.endswith(ext.rstrip('*')):
|
||
|
|
should_include = True
|
||
|
|
break
|
||
|
|
elif ext == '':
|
||
|
|
# 无扩展名文件(Linux可执行文件)
|
||
|
|
if not os.path.splitext(file)[1]:
|
||
|
|
should_include = True
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
# 普通扩展名匹配
|
||
|
|
if file.lower().endswith(ext.lower()):
|
||
|
|
should_include = True
|
||
|
|
break
|
||
|
|
|
||
|
|
if should_include:
|
||
|
|
binary_files.append(file_path)
|
||
|
|
print(f" 找到: {os.path.relpath(file_path, search_dir)}")
|
||
|
|
|
||
|
|
return binary_files
|
||
|
|
|
||
|
|
def create_zip_package(files, output_filename, base_dirs):
|
||
|
|
"""
|
||
|
|
创建ZIP压缩包
|
||
|
|
"""
|
||
|
|
print(f"创建ZIP包: {output_filename}")
|
||
|
|
|
||
|
|
with zipfile.ZipFile(output_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||
|
|
for file_path in files:
|
||
|
|
# 确定在ZIP中的相对路径
|
||
|
|
arcname = None
|
||
|
|
|
||
|
|
# 尝试找到最合适的基目录
|
||
|
|
for base_dir in base_dirs:
|
||
|
|
if base_dir and file_path.startswith(base_dir):
|
||
|
|
# 使用相对于基目录的路径
|
||
|
|
rel_path = os.path.relpath(file_path, base_dir)
|
||
|
|
# 保持目录结构
|
||
|
|
arcname = rel_path
|
||
|
|
break
|
||
|
|
|
||
|
|
# 如果没有找到合适的基目录,只使用文件名
|
||
|
|
if not arcname:
|
||
|
|
arcname = os.path.basename(file_path)
|
||
|
|
|
||
|
|
# 确保路径使用正斜杠
|
||
|
|
arcname = arcname.replace('\\', '/')
|
||
|
|
|
||
|
|
zipf.write(file_path, arcname)
|
||
|
|
print(f" 添加: {arcname}")
|
||
|
|
|
||
|
|
print(f"ZIP包创建完成: {output_filename}")
|
||
|
|
print(f"包含文件数: {len(files)}")
|
||
|
|
|
||
|
|
def create_tar_gz_package(files, output_filename, base_dirs):
|
||
|
|
"""
|
||
|
|
创建tar.gz压缩包
|
||
|
|
"""
|
||
|
|
print(f"创建tar.gz包: {output_filename}")
|
||
|
|
|
||
|
|
with tarfile.open(output_filename, "w:gz") as tar:
|
||
|
|
for file_path in files:
|
||
|
|
# 确定在tar中的相对路径
|
||
|
|
arcname = None
|
||
|
|
|
||
|
|
# 尝试找到最合适的基目录
|
||
|
|
for base_dir in base_dirs:
|
||
|
|
if base_dir and file_path.startswith(base_dir):
|
||
|
|
# 使用相对于基目录的路径
|
||
|
|
rel_path = os.path.relpath(file_path, base_dir)
|
||
|
|
# 保持目录结构
|
||
|
|
arcname = rel_path
|
||
|
|
break
|
||
|
|
|
||
|
|
# 如果没有找到合适的基目录,只使用文件名
|
||
|
|
if not arcname:
|
||
|
|
arcname = os.path.basename(file_path)
|
||
|
|
|
||
|
|
tar.add(file_path, arcname=arcname, recursive=False)
|
||
|
|
print(f" 添加: {arcname}")
|
||
|
|
|
||
|
|
print(f"tar.gz包创建完成: {output_filename}")
|
||
|
|
print(f"包含文件数: {len(files)}")
|
||
|
|
|
||
|
|
def main():
|
||
|
|
"""主函数"""
|
||
|
|
if len(sys.argv) != 2:
|
||
|
|
print("用法: python package_binaries.py <version_header_file>")
|
||
|
|
print("示例: python package_binaries.py Tools.h")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
header_file = sys.argv[1]
|
||
|
|
|
||
|
|
try:
|
||
|
|
# 1. 解析版本头文件
|
||
|
|
print(f"解析文件: {header_file}")
|
||
|
|
info = parse_version_header(header_file)
|
||
|
|
|
||
|
|
project_name = info['PROJECT_NAME']
|
||
|
|
git_commit = info['VERSION_GIT_COMMIT']
|
||
|
|
git_branch = info['VERSION_GIT_BRANCH']
|
||
|
|
runtime_dir = normalize_path(info.get('CMAKE_RUNTIME_OUTPUT_DIRECTORY', ''))
|
||
|
|
library_dir = normalize_path(info.get('LIBRARY_OUTPUT_PATH', ''))
|
||
|
|
|
||
|
|
print(f"项目: {project_name}")
|
||
|
|
print(f"分支: {git_branch}")
|
||
|
|
print(f"提交: {git_commit}")
|
||
|
|
print(f"运行时目录: {runtime_dir}")
|
||
|
|
print(f"库目录: {library_dir}")
|
||
|
|
|
||
|
|
# 2. 收集二进制文件
|
||
|
|
print("\n收集二进制文件...")
|
||
|
|
binary_files = collect_binary_files(runtime_dir, library_dir)
|
||
|
|
|
||
|
|
if not binary_files:
|
||
|
|
print("错误: 未找到任何二进制文件")
|
||
|
|
print(f"请检查以下目录:")
|
||
|
|
if runtime_dir:
|
||
|
|
print(f" - {runtime_dir}")
|
||
|
|
if library_dir:
|
||
|
|
print(f" - {library_dir}")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
print(f"找到 {len(binary_files)} 个二进制文件")
|
||
|
|
|
||
|
|
# 3. 创建输出文件名
|
||
|
|
# 清理分支名(替换特殊字符)
|
||
|
|
safe_branch = re.sub(r'[\\/*?:"<>|]', "_", git_branch)
|
||
|
|
output_basename = f"{project_name}-{safe_branch}-{git_commit}"
|
||
|
|
|
||
|
|
# 4. 根据操作系统选择压缩格式
|
||
|
|
current_os = platform.system().lower()
|
||
|
|
|
||
|
|
# 确定要搜索的基目录(用于保持相对路径)
|
||
|
|
base_dirs = []
|
||
|
|
if runtime_dir:
|
||
|
|
base_dirs.append(runtime_dir)
|
||
|
|
if library_dir:
|
||
|
|
base_dirs.append(library_dir)
|
||
|
|
|
||
|
|
if 'windows' in current_os:
|
||
|
|
output_file = f"{output_basename}.zip"
|
||
|
|
print(f"\n在Windows系统,创建ZIP包...")
|
||
|
|
create_zip_package(binary_files, output_file, base_dirs)
|
||
|
|
else:
|
||
|
|
output_file = f"{output_basename}.tar.gz"
|
||
|
|
print(f"\n在{current_os.capitalize()}系统,创建tar.gz包...")
|
||
|
|
create_tar_gz_package(binary_files, output_file, base_dirs)
|
||
|
|
|
||
|
|
# 5. 输出结果
|
||
|
|
file_size = os.path.getsize(output_file) / (1024 * 1024) # MB
|
||
|
|
print(f"\n✓ 打包完成!")
|
||
|
|
print(f"输出文件: {output_file}")
|
||
|
|
print(f"文件大小: {file_size:.2f} MB")
|
||
|
|
print(f"包含文件: {len(binary_files)} 个")
|
||
|
|
|
||
|
|
# 显示文件列表
|
||
|
|
print("\n文件列表:")
|
||
|
|
for i, file_path in enumerate(binary_files, 1):
|
||
|
|
# 显示相对于工作目录的路径
|
||
|
|
rel_path = os.path.relpath(file_path)
|
||
|
|
print(f" {i:3d}. {rel_path}")
|
||
|
|
|
||
|
|
except FileNotFoundError as e:
|
||
|
|
print(f"错误: {e}", file=sys.stderr)
|
||
|
|
sys.exit(1)
|
||
|
|
except ValueError as e:
|
||
|
|
print(f"错误: {e}", file=sys.stderr)
|
||
|
|
sys.exit(1)
|
||
|
|
except Exception as e:
|
||
|
|
print(f"未知错误: {e}", file=sys.stderr)
|
||
|
|
import traceback
|
||
|
|
traceback.print_exc()
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|