diff --git a/app/code_page/bsp_interface.py b/app/code_page/bsp_interface.py index 6244ce8..efbc26c 100644 --- a/app/code_page/bsp_interface.py +++ b/app/code_page/bsp_interface.py @@ -661,8 +661,8 @@ def patch_uart_interrupts(project_path, uart_instances): flags=re.DOTALL ) - with open(it_path, "w", encoding="utf-8") as f: - f.write(code) + # 使用save_with_preserve保存文件以保留用户区域 + CodeGenerator.save_with_preserve(it_path, code) class bsp_uart(BspPeripheralBase): diff --git a/app/code_page/device_interface.py b/app/code_page/device_interface.py index 4ccf3f9..287d52e 100644 --- a/app/code_page/device_interface.py +++ b/app/code_page/device_interface.py @@ -75,10 +75,8 @@ def generate_device_header(project_path, enabled_devices): replacement = f'/* AUTO GENERATED SIGNALS BEGIN */\n{signals_text}\n/* AUTO GENERATED SIGNALS END */' content = re.sub(pattern, replacement, content, flags=re.DOTALL) - # 保存文件 - os.makedirs(os.path.dirname(dst_path), exist_ok=True) - with open(dst_path, 'w', encoding='utf-8') as f: - f.write(content) + # 使用save_with_preserve保存文件以保留用户区域 + CodeGenerator.save_with_preserve(dst_path, content) @@ -251,32 +249,20 @@ class DeviceSimple(QWidget): dst_path = os.path.join(self.project_path, f"User/device/{filename}") if os.path.exists(src_path): - # 头文件和源文件都做变量替换 + # 读取模板文件内容 with open(src_path, 'r', encoding='utf-8') as f: content = f.read() + + # 替换BSP设备名称 for var_name, device_name in bsp_config.items(): content = content.replace(var_name, device_name) - os.makedirs(os.path.dirname(dst_path), exist_ok=True) - with open(dst_path, 'w', encoding='utf-8') as f: - f.write(content) - + + # 根据文件类型选择保存方式 if file_type == 'header': # 头文件需要保留用户区域 - os.makedirs(os.path.dirname(dst_path), exist_ok=True) - with open(src_path, 'r', encoding='utf-8') as f: - content = f.read() CodeGenerator.save_with_preserve(dst_path, content) - - elif file_type == 'source': - # 源文件需要替换BSP设备名称 - with open(src_path, 'r', encoding='utf-8') as f: - content = f.read() - - # 替换BSP设备名称 - for var_name, device_name in bsp_config.items(): - content = content.replace(var_name, device_name) - - # 保存文件 + else: + # 源文件直接保存(不需要保留用户区域) os.makedirs(os.path.dirname(dst_path), exist_ok=True) with open(dst_path, 'w', encoding='utf-8') as f: f.write(content) diff --git a/app/tools/code_generator.py b/app/tools/code_generator.py index 627bc9d..5549df1 100644 --- a/app/tools/code_generator.py +++ b/app/tools/code_generator.py @@ -35,7 +35,9 @@ class CodeGenerator: def save_file(content: str, file_path: str) -> bool: """保存文件""" try: - os.makedirs(os.path.dirname(file_path), exist_ok=True) + dir_path = os.path.dirname(file_path) + if dir_path: # 只有当目录路径不为空时才创建 + os.makedirs(dir_path, exist_ok=True) with open(file_path, 'w', encoding='utf-8') as f: f.write(content) return True @@ -176,6 +178,11 @@ class CodeGenerator: 用户代码... /* USER REGION_NAME END */ + 支持的格式示例: + - /* USER REFEREE BEGIN */ ... /* USER REFEREE END */ + - /* USER CODE BEGIN */ ... /* USER CODE END */ + - /* USER CUSTOM_NAME BEGIN */ ... /* USER CUSTOM_NAME END */ + Args: new_code: 新的代码内容 old_code: 旧的代码内容 @@ -186,23 +193,40 @@ class CodeGenerator: if not old_code: return new_code + # 更灵活的正则表达式,支持更多格式的用户区域标记 + # 匹配 /* USER 任意字符 BEGIN */ ... /* USER 相同字符 END */ pattern = re.compile( - r"/\*\s*(USER [A-Z0-9_ ]+)\s*BEGIN\s*\*/(.*?)/\*\s*\1\s*END\s*\*/", - re.DOTALL + r"/\*\s*USER\s+([A-Za-z0-9_\s]+?)\s+BEGIN\s*\*/(.*?)/\*\s*USER\s+\1\s+END\s*\*/", + re.DOTALL | re.IGNORECASE ) # 提取旧代码中的所有用户区域 - old_regions = {m.group(1): m.group(2) for m in pattern.finditer(old_code)} + old_regions = {} + for match in pattern.finditer(old_code): + region_name = match.group(1).strip() + region_content = match.group(2) + old_regions[region_name.upper()] = region_content - def repl(m): - region_name = m.group(1) + # 替换函数 + def repl(match): + region_name = match.group(1).strip().upper() + current_content = match.group(2) old_content = old_regions.get(region_name) + if old_content is not None: - # 替换为旧的用户内容 - return m.group(0).replace(m.group(2), old_content) - return m.group(0) + # 直接替换中间的内容,保持原有的注释标记不变 + return match.group(0).replace(current_content, old_content) + + return match.group(0) - return pattern.sub(repl, new_code) + # 应用替换 + result = pattern.sub(repl, new_code) + + # 调试信息:记录找到的用户区域 + if old_regions: + print(f"保留了 {len(old_regions)} 个用户区域: {list(old_regions.keys())}") + + return result @staticmethod def save_with_preserve(file_path: str, new_code: str) -> bool: @@ -229,7 +253,9 @@ class CodeGenerator: final_code = CodeGenerator.preserve_all_user_regions(new_code, old_code) # 确保目录存在 - os.makedirs(os.path.dirname(file_path), exist_ok=True) + dir_path = os.path.dirname(file_path) + if dir_path: # 只有当目录路径不为空时才创建 + os.makedirs(dir_path, exist_ok=True) # 保存文件 with open(file_path, "w", encoding="utf-8") as f: @@ -450,6 +476,11 @@ class CodeGenerator: def extract_user_regions(code: str) -> Dict[str, str]: """从代码中提取所有用户区域 + 支持提取各种格式的用户区域: + - /* USER REFEREE BEGIN */ ... /* USER REFEREE END */ + - /* USER CODE BEGIN */ ... /* USER CODE END */ + - /* USER CUSTOM_NAME BEGIN */ ... /* USER CUSTOM_NAME END */ + Args: code: 要提取的代码内容 @@ -459,9 +490,68 @@ class CodeGenerator: if not code: return {} + # 使用与preserve_all_user_regions相同的正则表达式 pattern = re.compile( - r"/\*\s*USER CODE BEGIN ([A-Za-z0-9_ ]+)\s*\*/(.*?)/\*\s*USER CODE END \1\s*\*/", - re.DOTALL + r"/\*\s*USER\s+([A-Za-z0-9_\s]+?)\s+BEGIN\s*\*/(.*?)/\*\s*USER\s+\1\s+END\s*\*/", + re.DOTALL | re.IGNORECASE ) - return {m.group(1): m.group(2) for m in pattern.finditer(code)} \ No newline at end of file + regions = {} + for match in pattern.finditer(code): + region_name = match.group(1).strip().upper() + region_content = match.group(2) + regions[region_name] = region_content + + return regions + + @staticmethod + def debug_user_regions(new_code: str, old_code: str, verbose: bool = False) -> Dict[str, Dict[str, str]]: + """调试用户区域,显示新旧内容的对比 + + Args: + new_code: 新的代码内容 + old_code: 旧的代码内容 + verbose: 是否输出详细信息 + + Returns: + Dict: 包含所有用户区域信息的字典 + """ + if verbose: + print("=== 用户区域调试信息 ===") + + new_regions = CodeGenerator.extract_user_regions(new_code) + old_regions = CodeGenerator.extract_user_regions(old_code) + + all_region_names = set(new_regions.keys()) | set(old_regions.keys()) + + result = {} + + for region_name in sorted(all_region_names): + new_content = new_regions.get(region_name, "") + old_content = old_regions.get(region_name, "") + + result[region_name] = { + "new_content": new_content, + "old_content": old_content, + "will_preserve": bool(old_content), + "exists_in_new": region_name in new_regions, + "exists_in_old": region_name in old_regions + } + + if verbose: + status = "保留旧内容" if old_content else "使用新内容" + print(f"\n区域: {region_name} ({status})") + print(f" 新模板中存在: {'是' if region_name in new_regions else '否'}") + print(f" 旧文件中存在: {'是' if region_name in old_regions else '否'}") + + if new_content.strip(): + print(f" 新内容预览: {repr(new_content.strip()[:50])}...") + if old_content.strip(): + print(f" 旧内容预览: {repr(old_content.strip()[:50])}...") + + if verbose: + print(f"\n总计: {len(all_region_names)} 个用户区域") + preserve_count = sum(1 for info in result.values() if info["will_preserve"]) + print(f"将保留: {preserve_count} 个区域的旧内容") + + return result \ No newline at end of file diff --git a/check_releases.py b/check_releases.py deleted file mode 100644 index d2e8209..0000000 --- a/check_releases.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -""" -检查GitHub Releases API响应结构 -""" - -import requests -import json - -def check_releases_structure(): - """检查GitHub releases的API响应结构""" - try: - url = "https://api.github.com/repos/goldenfishs/MRobot/releases/latest" - response = requests.get(url, timeout=10) - - if response.status_code == 200: - data = response.json() - - print("Release信息:") - print(f"标签: {data.get('tag_name')}") - print(f"名称: {data.get('name')}") - print(f"发布时间: {data.get('published_at')}") - print(f"是否为预发布: {data.get('prerelease')}") - print(f"是否为草稿: {data.get('draft')}") - - print("\n可用的资源文件:") - assets = data.get('assets', []) - - if not assets: - print("❌ 没有找到任何资源文件") - print("建议在GitHub Release中上传安装包文件") - else: - for i, asset in enumerate(assets): - print(f" {i+1}. {asset['name']}") - print(f" 大小: {asset['size']} 字节") - print(f" 下载链接: {asset['browser_download_url']}") - print(f" 内容类型: {asset.get('content_type', 'unknown')}") - print() - - print(f"\n更新说明:\n{data.get('body', '无')}") - - else: - print(f"❌ API请求失败,状态码: {response.status_code}") - - except Exception as e: - print(f"❌ 检查失败: {e}") - -if __name__ == "__main__": - check_releases_structure() \ No newline at end of file diff --git a/code_generator_usage_check.md b/code_generator_usage_check.md deleted file mode 100644 index 0dc146f..0000000 --- a/code_generator_usage_check.md +++ /dev/null @@ -1,136 +0,0 @@ -# CodeGenerator 使用情况完整检查报告 - -## 检查目标 -确保 `CodeGenerator` 类在开发环境和打包exe环境中都能正常工作,不会出现 `local variable 'CodeGenerator' referenced before assignment` 错误。 - -## 已修复的问题 - -### 1. 静态方法中的导入问题 -✅ **已修复** - 以下静态方法已添加本地导入: - -- `app/code_generate_interface.py` - - `generate_code()` - 第270行已添加本地导入 - - `_load_csv_and_build_tree()` - 第348行已添加本地导入 - -- `app/code_page/component_interface.py` - - `component.generate_component()` - 第289行已添加本地导入 - -- `app/code_page/bsp_interface.py` - - `bsp.generate_bsp()` - 第1240行已添加本地导入 - -- `app/code_page/device_interface.py` - - `generate_device_header()` - 第44行已添加本地导入 - - `get_device_page()` - 第321行已添加本地导入 - -### 2. 相对导入问题 -✅ **已修复** - 统一使用绝对导入: -- `app/code_page/component_interface.py` 第321行的相对导入已改为绝对导入 - -### 3. 缓存和性能优化 -✅ **已优化**: -- 添加了 `_assets_dir_cache` 和 `_assets_dir_initialized` 缓存机制 -- 优化了 `get_template_dir()` 方法,减少重复日志输出 -- 改进了路径计算,避免重复的文件系统操作 - -## 安全的使用场景 - -### 1. 实例方法中的使用(✅ 安全) -这些使用 `CodeGenerator` 的地方都是在类的实例方法中,可以正常使用顶层导入: - -- `ComponentSimple.__init__()` - 第108行 -- `ComponentSimple._generate_component_code_internal()` - 第172行 -- `ComponentSimple._get_component_template_dir()` - 第182行 -- `ComponentSimple._save_config()` - 第186, 191行 -- `ComponentSimple._load_config()` - 第195行 -- `DevicePageBase.__init__()` 及相关方法 -- `BspPeripheralBase` 各种实例方法 -- `DataInterface.show_user_code_files()` - 已有本地导入 -- `DataInterface.generate_code()` - 已有本地导入 -- `DataInterface.generate_task_code()` - 已有本地导入 - -### 2. 模块级别函数(需要验证) -以下独立函数需要确认是否使用了 `CodeGenerator`: - -- `load_device_config()` -- `get_available_bsp_devices()` -- `load_descriptions()` (bsp) -- `get_available_*()` 系列函数 - -## 打包环境优化 - -### 1. 路径处理优化 -✅ **已优化** `get_assets_dir()` 方法: -- 优先使用 `sys._MEIPASS`(PyInstaller临时目录) -- 后备使用可执行文件目录 -- 增加工作目录查找作为最后选择 -- 改进了开发环境的目录查找逻辑 -- 添加了路径规范化处理 - -### 2. 错误处理改进 -✅ **已改进**: -- 更好的错误提示信息 -- 只在第一次访问路径时显示警告 -- 防止重复日志输出 - -## 建议的进一步改进 - -### 1. 添加环境检测工具方法 -```python -@staticmethod -def is_frozen(): - """检测是否在打包环境中运行""" - return getattr(sys, 'frozen', False) - -@staticmethod -def get_base_path(): - """获取基础路径,自动适配开发/打包环境""" - if CodeGenerator.is_frozen(): - return getattr(sys, '_MEIPASS', os.path.dirname(sys.executable)) - else: - return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -``` - -### 2. 配置验证方法 -```python -@staticmethod -def validate_assets(): - """验证assets目录是否存在并包含必要文件""" - assets_dir = CodeGenerator.get_assets_dir() - required_dirs = ["User_code/bsp", "User_code/component", "User_code/device"] - - for req_dir in required_dirs: - path = os.path.join(assets_dir, req_dir) - if not os.path.exists(path): - return False, f"缺少必要目录: {path}" - return True, "Assets目录验证通过" -``` - -## 测试清单 - -### 开发环境测试 -- [ ] 启动应用程序无错误 -- [ ] 生成BSP代码功能正常 -- [ ] 生成Component代码功能正常 -- [ ] 生成Device代码功能正常 -- [ ] 路径解析正确 -- [ ] 无重复日志输出 - -### 打包环境测试 -- [ ] 使用PyInstaller打包成exe -- [ ] exe启动无错误 -- [ ] 所有代码生成功能正常 -- [ ] assets目录正确定位 -- [ ] 模板文件正确加载 -- [ ] 配置文件读写正常 - -## 总结 - -经过完整检查和修复,现在的代码应该能够在开发环境和打包环境中都正常工作。主要改进包括: - -1. **导入安全性**:所有静态方法都添加了本地导入 -2. **路径处理**:优化了assets目录的查找逻辑 -3. **性能优化**:添加了缓存机制,减少重复计算 -4. **错误处理**:改进了错误提示和日志输出 -5. **兼容性**:确保开发和打包环境的兼容性 - -建议在发布前进行完整的功能测试,特别是在打包后的exe环境中测试所有代码生成功能。 \ No newline at end of file