# build command # pyinstaller --onefile --distpath . --workpath ./build --specpath ./build generateVars.py # start script # generateVars.exe F:\Work\Projects\TMS\TMS_new_bus\ Src/DebugTools/vars.xml Src/DebugTools import sys import os import re import xml.etree.ElementTree as ET from pathlib import Path import argparse # === Словарь соответствия типов XML → DebugVarType_t === type_map = dict([ *[(k, 'pt_int8') for k in ('signed char', 'char')], *[(k, 'pt_int16') for k in ('int', 'int16', 'short')], *[(k, 'pt_int32') for k in ('long', 'int32', '_iqx')], *[(k, 'pt_int64') for k in ('long long', 'int64')], *[(k, 'pt_uint8') for k in ('unsigned char',)], *[(k, 'pt_uint16') for k in ('unsigned int', 'unsigned short', 'Uint16')], *[(k, 'pt_uint32') for k in ('unsigned long', 'Uint32')], *[(k, 'pt_uint64') for k in ('unsigned long long', 'Uint64')], ('struct', 'pt_struct'), ('union', 'pt_union'), *[(k, 'pt_ptr_int8') for k in ('signed char*', 'char*')], *[(k, 'pt_ptr_int16') for k in ('int*', 'short*')], *[(k, 'pt_ptr_int32') for k in ('long*',)], *[(k, 'pt_ptr_uint8') for k in ('unsigned char*',)], *[(k, 'pt_ptr_uint16') for k in ('unsigned int*', 'unsigned short*')], *[(k, 'pt_ptr_uint32') for k in ('unsigned long*',)], ('unsigned long long*', 'pt_int64'), ('struct*', 'pt_ptr_struct'), ('union*', 'pt_ptr_union'), *[(k, 'pt_arr_int8') for k in ('signed char[]', 'char[]')], *[(k, 'pt_arr_int16') for k in ('int[]', 'short[]')], *[(k, 'pt_arr_int32') for k in ('long[]',)], *[(k, 'pt_arr_uint8') for k in ('unsigned char[]',)], *[(k, 'pt_arr_uint16') for k in ('unsigned int[]', 'unsigned short[]')], *[(k, 'pt_arr_uint32') for k in ('unsigned long[]',)], *[(k, 'pt_float') for k in ('float', 'float32')], ('struct[]', 'pt_arr_struct'), ('union[]', 'pt_arr_union'), ]) def map_type_to_pt(typename, varname=None, typedef_map=None): typename_orig = typename.strip() # Убираем const и volatile (чтобы не мешали проверке) for qualifier in ('const', 'volatile'): typename_orig = typename_orig.replace(qualifier, '') typename_orig = typename_orig.strip() # Проверка наличия массива [] или указателя * is_array = bool(re.search(r'\[.*\]', typename_orig)) is_ptr = '*' in typename_orig # Убираем все [] и * для получения базового типа typename_base = re.sub(r'\[.*?\]', '', typename_orig).replace('*', '').strip() typedef_maybe = typename_base if typename_base.startswith('struct'): typename_base = 'struct' if typename_base.startswith('union'): typename_base = 'union' # Добавляем [] или * к базовому типу для поиска if is_array: typename_base = typename_base + '[]' elif is_ptr: typename_base = typename_base + '*' else: typename_base = typename_base if typename_base in type_map: return type_map[typename_base] if '_iq' in typename_base and '_iqx' in type_map: return type_map['_iqx'] # Если есть typedef_map — пробуем по нему if typedef_map and typedef_maybe in typedef_map: resolved = typedef_map[typedef_maybe].strip() # Убираем const и volatile for qualifier in ('const', 'volatile'): resolved = resolved.replace(qualifier, '') resolved = resolved.strip() # Получаем базовый тип из typedef-а base_t = re.sub(r'\[.*?\]', '', resolved).replace('*', '').strip() if base_t.startswith('struct'): base_t = 'struct' if base_t.startswith('union'): base_t = 'union' if is_array: base_t += '[]' elif is_ptr: base_t += '*' # Пробуем по базовому имени if base_t in type_map: return type_map[base_t] if '_iq' in base_t and '_iqx' in type_map: return type_map['_iqx'] return 'pt_unknown' def get_iq_define(vtype): # Убираем все скобки массива, например: _iq[5] → _iq vtype = re.sub(r'\[.*?\]', '', vtype).strip() if '_iq' in vtype: # Преобразуем _iqXX в t_iqXX return 't' + vtype[vtype.index('_iq'):] else: return 't_iq_none' def add_new_vars_to_xml(proj_path, xml_rel_path, output_path): """ new_vars — dict: ключ = имя переменной, значение = словарь с info (type, file, extern, static, enable, show_var и т.п.) Если переменной нет в XML (в ), добавляем её и сохраняем XML-файл. Возвращает True если что-то добавлено и XML перезаписан, иначе False. """ # Считываем существующие переменные parsed_vars = {} if os.path.isfile(output_path): with open(output_path, 'r', encoding='utf-8', errors='ignore') as f: for line in f: # {(char *)&some.deep.var.name , pt_uint16 , t_iq15 , "ShortName"}, m = re.match( r'{\s*\(char\s*\*\)\s*&([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*(pt_\w+)\s*,\s*(t?iq_\w+)\s*,\s*"([^"]+)"', line) if m: full_varname = m.group(1) # e.g., some.deep.var.name pt_type = m.group(2) iq_type = m.group(3) shortname = m.group(4) parsed_vars[full_varname] = { 'pt_type': pt_type, 'iq_type': iq_type, 'enable': True, 'show_var': True, 'shortname': shortname, 'return_type': 'int', 'type': '', # Можешь дополнить из externs 'file': '', # Можешь дополнить из externs 'extern': False, 'static': False, 'name': full_varname # Сохраняем исходное имя переменной } if not parsed_vars: print("[INFO] Не удалось найти ни одной переменной в debug_vars.c") return False xml_full_path = os.path.join(proj_path, xml_rel_path) xml_full_path = os.path.normpath(xml_full_path) tree = ET.parse(xml_full_path) root = tree.getroot() vars_section = root.find("variables") if vars_section is None: vars_section = ET.SubElement(root, "variables") existing_var_names = {v.attrib['name'] for v in vars_section.findall("var")} added_count = 0 # 3. Добавляем переменные, которых нет в XML for name, info in parsed_vars.items(): if name in existing_var_names: # Уже есть — обновляем enable, если нужно existing_elem = vars_section.find(f"./var[@name='{name}']") if existing_elem is not None: manual_elem = existing_elem.find("manual") if manual_elem and manual_elem.text == "true": show_elem = existing_elem.find("show_var") if show_elem is None: show_elem = ET.SubElement(existing_elem, "enable") enable_elem.text = "true" enable_elem = existing_elem.find("enable") if enable_elem is None: enable_elem = ET.SubElement(existing_elem, "enable") enable_elem.text = "true" added_count += 1 continue var_elem = ET.SubElement(vars_section, "var", {"name": name}) manual = ET.SubElement(var_elem, 'manual') manual.text = 'true' for key, val in info.items(): elem = ET.SubElement(var_elem, key) if isinstance(val, bool): elem.text = "true" if val else "false" else: elem.text = str(val) added_count += 1 if added_count > 0: ET.indent(tree, space=" ", level=0) tree.write(xml_full_path, encoding="utf-8", xml_declaration=True) print(f"[INFO] В XML добавлено новых переменных: {added_count}") return True else: print("[INFO] Все переменные уже есть в XML.") return False def read_vars_from_xml(proj_path, xml_rel_path): xml_full_path = os.path.join(proj_path, xml_rel_path) xml_full_path = os.path.normpath(xml_full_path) tree = ET.parse(xml_full_path) root = tree.getroot() vars_section = root.find("variables") includes_section = root.find("includes") externs_section = root.find("externs") unique_vars = {} vars_need_extern = {} # Читаем переменные из for var in vars_section.findall("var"): name = var.attrib["name"] var_info = {} # Обрабатываем дочерние элементы (type, file, extern, static и т.п.) for child in var: text = child.text.strip() if child.text else "" # Конвертируем "true"/"false" в bool для extern и static if child.tag in ("extern", "static"): var_info[child.tag] = (text.lower() == "true") else: var_info[child.tag] = text if child.tag == "enable": var_info["enable"] = (text.lower() == "true") # Обрабатываем путь к файлу (если есть) if "file" in var_info: file_rel = var_info["file"] file_full = os.path.normpath(os.path.join(proj_path, file_rel)) var_info["file"] = file_full unique_vars[name] = var_info # Читаем include-файлы (относительные) и преобразуем в полные пути include_files = [] for node in includes_section.findall("file"): rel_path = node.text full_path = os.path.normpath(os.path.join(proj_path, rel_path)) include_files.append(full_path) # Читаем extern переменные из for var in externs_section.findall("var"): name = var.attrib["name"] type_ = var.find("type").text file_rel = var.find("file").text file_full = os.path.normpath(os.path.join(proj_path, file_rel)) vars_need_extern[name] = { "type": type_, "file": file_full } return unique_vars, include_files, vars_need_extern def generate_vars_file(proj_path, xml_path, output_dir): output_dir = os.path.join(proj_path, output_dir) os.makedirs(output_dir, exist_ok=True) output_path = os.path.join(output_dir, 'debug_vars.c') # Запись новых переменных для в XML add_new_vars_to_xml(proj_path, xml_path, output_path) # Генерируем новые переменные vars, includes, externs = read_vars_from_xml(proj_path, xml_path) # Сортируем новые переменные по алфавиту по имени sorted_new_debug_vars = dict(sorted(vars.items())) new_debug_vars = {} def is_true(val): # Преобразуем значение к строке, если оно не None # и сравниваем с 'true' в нижнем регистре return str(val).lower() == 'true' for vname, info in vars.items(): # Проверяем, что show_var и enable включены (строки как строки 'true') if not is_true(info.get('show_var', 'false')): continue if not is_true(info.get('enable', 'false')): continue vtype = info["type"] is_extern = info["extern"] is_static = info.get("static", False) if is_static: continue # пропускаем static переменные path = info["file"] iq_type = info.get('iq_type') if not iq_type: iq_type = get_iq_define(vtype) pt_type = info.get('pt_type') if not pt_type: pt_type = map_type_to_pt(vtype, vname) # Дополнительные поля, например комментарий comment = info.get("comment", "") short_name = info.get("shortname", f'"{vname}"') if pt_type not in ('pt_struct', 'pt_union'): formated_name = f'"{vname}"' # Добавим комментарий после записи, если он есть comment_str = f' // {comment}' if comment else '' line = f'{{(char *)&{vname:<41} , {pt_type:<21} , {iq_type:<21} , {short_name:<42}}}, \\{comment_str}' new_debug_vars[vname] = line else: continue # Если тип переменной — структура, добавляем поля base_type = vtype.split()[0] # Удаляем символы указателей '*' и всю квадратную скобку с содержимым (например [10]) base_type = re.sub(r'\*|\[[^\]]*\]', '', base_type).strip() if base_type in all_structs: add_struct_fields(new_debug_vars, vname, base_type, all_structs, existing_debug_vars) # Объединяем все переменные all_debug_lines = new_debug_vars.values() out_lines = [] out_lines.append("// Этот файл сгенерирован автоматически") out_lines.append(f'#include "debug_tools.h"') out_lines.append('\n\n// Инклюды для доступа к переменным') for incf in includes: filename = os.path.basename(incf) out_lines.append(f'#include "{filename}"') out_lines.append('\n\n// Экстерны для доступа к переменным') for vname, info in externs.items(): vtype = info["type"].strip() is_static = info.get("static", False) # <-- добавлено if is_static: continue # пропускаем static переменные # Попытка выделить размер массива из типа, например int[20] array_match = re.match(r'^(.*?)(\s*\[.*\])$', vtype) if array_match: base_type = array_match.group(1).strip() array_size = array_match.group(2).strip() out_lines.append(f'extern {base_type} {vname}{array_size};') else: # Если не массив — обычный extern out_lines.append(f'extern {vtype} {vname};') out_lines.append(f'\n\n// Определение массива с указателями на переменные для отладки') out_lines.append(f'int DebugVar_Qnt = {len(all_debug_lines)};') out_lines.append('#pragma DATA_SECTION(dbg_vars,".dbgvar_info")') out_lines.append('DebugVar_t dbg_vars[] = {\\') out_lines.extend(all_debug_lines) out_lines.append('};') out_lines.append('') # Выберем кодировку для записи файла # Если встречается несколько, возьмем первую из set enc_to_write = 'cp1251' #print("== GLOBAL VARS FOUND ==") #for vname, (vtype, path) in vars_in_c.items(): #print(f"{vtype:<20} {vname:<40} // {path}") with open(output_path, 'w', encoding=enc_to_write) as f: f.write('\n'.join(out_lines)) print(f'Файл debug_vars.c сгенерирован в кодировке, переменных: {len(all_debug_lines)}') #generate_vars_file("E:/.WORK/TMS/TMS_new_bus/", "Src/DebugTools/vars.xml", "E:/.WORK/TMS/TMS_new_bus/Src/DebugTools/") def main(): parser = argparse.ArgumentParser( description="Generate debug_vars.c from project XML and output directory.", epilog="""\ Usage example: %(prog)s /absolute/path/to/project /absolute/path/to/project/Src/DebugTools/vars.xml /absolute/path/to/project/Src/DebugTools/ """, formatter_class=argparse.RawDescriptionHelpFormatter, add_help=False ) parser.add_argument("proj_path", help="Absolute path to the project root directory") parser.add_argument("xml_path", help="Absolute path to the XML file (must be inside project)") parser.add_argument("output_dir", help="Absolute path to output directory (must be inside project)") parser.add_argument("-h", "--help", action="store_true", help="Show this help message and exit") # Show help if requested if "-h" in sys.argv or "--help" in sys.argv: parser.print_help() sys.exit(0) # Check minimum args count if len(sys.argv) < 4: print("Error: insufficient arguments.\n") print("Usage example:") print(f" {os.path.basename(sys.argv[0])} /absolute/path/to/project /absolute/path/to/project/Src/DebugTools/vars.xml /absolute/path/to/project/Src/DebugTools/\n") sys.exit(1) args = parser.parse_args() # Normalize absolute paths proj_path = os.path.abspath(args.proj_path) xml_path_abs = os.path.abspath(args.xml_path) output_dir_abs = os.path.abspath(args.output_dir) # Check proj_path is directory if not os.path.isdir(proj_path): print(f"Error: Project path '{proj_path}' is not a directory or does not exist.") sys.exit(1) # Check xml_path inside proj_path if not xml_path_abs.startswith(proj_path + os.sep): print(f"Error: XML path '{xml_path_abs}' is not inside the project path '{proj_path}'.") sys.exit(1) # Check output_dir inside proj_path if not output_dir_abs.startswith(proj_path + os.sep): print(f"Error: Output directory '{output_dir_abs}' is not inside the project path '{proj_path}'.") sys.exit(1) # Convert xml_path and output_dir to relative paths *relative to proj_path* xml_path_rel = os.path.relpath(xml_path_abs, proj_path) output_dir_rel = os.path.relpath(output_dir_abs, proj_path) if not os.path.isdir(proj_path): print(f"Error: Project path '{proj_path}' не является директорией или не существует.") sys.exit(1) generate_vars_file(proj_path, xml_path_rel, output_dir_rel) if __name__ == "__main__": main() def run_generate(proj_path, xml_path, output_dir): import os # Normalize absolute paths proj_path = os.path.abspath(proj_path) xml_path_abs = os.path.abspath(xml_path) output_dir_abs = os.path.abspath(output_dir) # Проверка валидности путей if not os.path.isdir(proj_path): raise FileNotFoundError(f"Project path '{proj_path}' is not a directory or does not exist.") if not xml_path_abs.startswith(proj_path + os.sep): raise ValueError(f"XML path '{xml_path_abs}' is not inside the project path '{proj_path}'.") if not output_dir_abs.startswith(proj_path + os.sep): raise ValueError(f"Output directory '{output_dir_abs}' is not inside the project path '{proj_path}'.") # Преобразуем к относительным путям относительно проекта xml_path_rel = os.path.relpath(xml_path_abs, proj_path) output_dir_rel = os.path.relpath(output_dir_abs, proj_path) # Запускаем генерацию generate_vars_file(proj_path, xml_path_rel, output_dir_rel)