#!/usr/bin/env python3 """ 解析版本头文件并打包二进制文件的脚本 用法: python package_binaries.py """ 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 ") 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()