diff --git a/app/code_generate_interface.py b/app/code_generate_interface.py index 18077fa..03c3d15 100644 --- a/app/code_generate_interface.py +++ b/app/code_generate_interface.py @@ -267,6 +267,7 @@ class CodeGenerateInterface(QWidget): """生成所有代码,包括未加载页面""" try: # 先收集所有页面名(从CSV配置文件读取) + from app.tools.code_generator import CodeGenerator # 在方法内重新导入确保可用 csv_path = os.path.join(CodeGenerator.get_assets_dir("User_code"), "config.csv") all_class_names = [] if os.path.exists(csv_path): @@ -344,6 +345,7 @@ class CodeGenerateInterface(QWidget): return "未找到.ioc文件" def _load_csv_and_build_tree(self): + from app.tools.code_generator import CodeGenerator # 在方法内重新导入确保可用 csv_path = os.path.join(CodeGenerator.get_assets_dir("User_code"), "config.csv") print(f"加载CSV路径: {csv_path}") if not os.path.exists(csv_path): diff --git a/app/code_page/bsp_interface.py b/app/code_page/bsp_interface.py index d49ba37..fe94e88 100644 --- a/app/code_page/bsp_interface.py +++ b/app/code_page/bsp_interface.py @@ -1235,6 +1235,9 @@ class bsp(QWidget): @staticmethod def generate_bsp(project_path, pages): """生成所有BSP代码""" + # 在方法开始时导入CodeGenerator以确保可用 + from app.tools.code_generator import CodeGenerator + # 自动添加 bsp.h src_bsp_h = os.path.join(CodeGenerator.get_assets_dir("User_code/bsp"), "bsp.h") dst_bsp_h = os.path.join(project_path, "User/bsp/bsp.h") diff --git a/app/code_page/component_interface.py b/app/code_page/component_interface.py index 289b520..c17e697 100644 --- a/app/code_page/component_interface.py +++ b/app/code_page/component_interface.py @@ -286,6 +286,9 @@ class component(QWidget): @staticmethod def generate_component(project_path, pages): """生成所有组件代码,处理依赖关系""" + # 在方法开始时导入CodeGenerator以确保可用 + from app.tools.code_generator import CodeGenerator + # 自动添加 component.h src_component_h = os.path.join(CodeGenerator.get_assets_dir("User_code/component"), "component.h") dst_component_h = os.path.join(project_path, "User/component/component.h") @@ -318,7 +321,6 @@ class component(QWidget): components_to_generate.add(dep_name) # 为没有对应页面但需要生成的依赖组件创建临时页面 - from ..tools.code_generator import CodeGenerator user_code_dir = CodeGenerator.get_assets_dir("User_code") for comp_name in components_to_generate: if comp_name not in component_pages: diff --git a/app/code_page/device_interface.py b/app/code_page/device_interface.py index 72523ea..5273f40 100644 --- a/app/code_page/device_interface.py +++ b/app/code_page/device_interface.py @@ -41,6 +41,7 @@ def get_available_bsp_devices(project_path, bsp_type, gpio_type=None): def generate_device_header(project_path, enabled_devices): """生成device.h文件""" + from app.tools.code_generator import CodeGenerator device_dir = CodeGenerator.get_assets_dir("User_code/device") template_path = os.path.join(device_dir, "device.h") @@ -318,6 +319,7 @@ class DeviceSimple(QWidget): def get_device_page(device_name, project_path): """根据设备名返回对应的页面类""" # 加载设备配置 + from app.tools.code_generator import CodeGenerator device_dir = CodeGenerator.get_assets_dir("User_code/device") config_path = os.path.join(device_dir, "config.yaml") device_configs = load_device_config(config_path) diff --git a/app/tools/code_generator.py b/app/tools/code_generator.py index 357ebcb..492abe1 100644 --- a/app/tools/code_generator.py +++ b/app/tools/code_generator.py @@ -7,6 +7,11 @@ import os class CodeGenerator: """通用代码生成器""" + # 添加类级别的缓存 + _assets_dir_cache = None + _assets_dir_initialized = False + _template_dir_logged = False + @staticmethod def load_template(template_path: str) -> str: """加载代码模板""" @@ -66,8 +71,12 @@ class CodeGenerator: # 使用统一的get_assets_dir方法来获取路径 template_dir = CodeGenerator.get_assets_dir("User_code/bsp") - print(f"模板目录路径: {template_dir}") - if not os.path.exists(template_dir): + # 只在第一次或出现问题时打印日志 + if not hasattr(CodeGenerator, '_template_dir_logged'): + print(f"模板目录路径: {template_dir}") + CodeGenerator._template_dir_logged = True + + if template_dir and not os.path.exists(template_dir): print(f"警告:模板目录不存在: {template_dir}") return template_dir @@ -80,31 +89,78 @@ class CodeGenerator: Returns: str: 完整的assets路径 """ - if getattr(sys, 'frozen', False): - # 打包后的环境 - 使用可执行文件所在目录而不是临时目录 - exe_dir = os.path.dirname(sys.executable) - assets_dir = os.path.join(exe_dir, "assets") - print(f"打包环境:尝试使用路径: {assets_dir}") + # 使用缓存机制,避免重复计算和日志输出 + if not CodeGenerator._assets_dir_initialized: + assets_dir = "" - # 如果exe_dir/assets不存在,尝试使用sys._MEIPASS作为后备 - if not os.path.exists(assets_dir) and hasattr(sys, '_MEIPASS'): - base_path = getattr(sys, '_MEIPASS') - assets_dir = os.path.join(base_path, "assets") - print(f"后备路径: {assets_dir}") + if getattr(sys, 'frozen', False): + # 打包后的环境 + print("检测到打包环境") + + # 优先使用sys._MEIPASS(PyInstaller的临时解包目录) + if hasattr(sys, '_MEIPASS'): + base_path = getattr(sys, '_MEIPASS') + assets_dir = os.path.join(base_path, "assets") + print(f"使用PyInstaller临时目录: {assets_dir}") + else: + # 后备方案:使用可执行文件所在目录 + exe_dir = os.path.dirname(sys.executable) + assets_dir = os.path.join(exe_dir, "assets") + print(f"使用可执行文件目录: {assets_dir}") + + # 如果都不存在,尝试其他可能的位置 + if not os.path.exists(assets_dir): + # 尝试从当前工作目录查找 + cwd_assets = os.path.join(os.getcwd(), "assets") + if os.path.exists(cwd_assets): + assets_dir = cwd_assets + print(f"从工作目录找到assets: {assets_dir}") + else: + print(f"警告:无法找到assets目录,使用默认路径: {assets_dir}") + else: + # 开发环境 + current_dir = os.path.dirname(os.path.abspath(__file__)) + + # 向上查找直到找到MRobot目录或到达根目录 + while current_dir != os.path.dirname(current_dir): # 防止无限循环 + if os.path.basename(current_dir) == 'MRobot': + break + parent = os.path.dirname(current_dir) + if parent == current_dir: # 已到达根目录 + break + current_dir = parent + + assets_dir = os.path.join(current_dir, "assets") + print(f"开发环境:使用路径: {assets_dir}") + + # 如果找不到,尝试从当前工作目录 + if not os.path.exists(assets_dir): + cwd_assets = os.path.join(os.getcwd(), "assets") + if os.path.exists(cwd_assets): + assets_dir = cwd_assets + print(f"开发环境后备:使用工作目录: {assets_dir}") + + # 缓存基础assets目录 + CodeGenerator._assets_dir_cache = assets_dir + CodeGenerator._assets_dir_initialized = True else: - # 开发环境 - current_dir = os.path.dirname(os.path.abspath(__file__)) - while os.path.basename(current_dir) != 'MRobot' and current_dir != '/': - current_dir = os.path.dirname(current_dir) - assets_dir = os.path.join(current_dir, "assets") - print(f"开发环境:使用路径: {assets_dir}") + # 使用缓存的路径 + assets_dir = CodeGenerator._assets_dir_cache or "" + # 构建完整路径 if sub_path: full_path = os.path.join(assets_dir, sub_path) else: full_path = assets_dir - if not os.path.exists(full_path): + # 规范化路径(处理路径分隔符) + full_path = os.path.normpath(full_path) + + # 只在第一次访问某个路径时检查并警告 + safe_sub_path = sub_path.replace('/', '_').replace('\\', '_') + warning_key = f"_warned_{safe_sub_path}" + if full_path and not os.path.exists(full_path) and not hasattr(CodeGenerator, warning_key): print(f"警告:资源目录不存在: {full_path}") + setattr(CodeGenerator, warning_key, True) return full_path \ No newline at end of file diff --git a/code_generator_usage_check.md b/code_generator_usage_check.md new file mode 100644 index 0000000..0dc146f --- /dev/null +++ b/code_generator_usage_check.md @@ -0,0 +1,136 @@ +# 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