# pyinstaller --onefile --distpath . --workpath ./build --specpath ./build scanVars.py import sys import os import re import argparse from pathlib import Path # === Словарь соответствия типов 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')], *[(k, 'pt_ptr_int8') for k in ('signed 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'), *[(k, 'pt_arr_int8') for k in ('signed 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_struct'), ('union', 'pt_union'), ]) def parse_makefile(makefile_path): makefile_dir = os.path.dirname(makefile_path) project_root = os.path.dirname(makefile_dir) # поднялись из Debug with open(makefile_path, 'r', encoding='utf-8') as f: lines = f.readlines() objs_lines = [] collecting = False for line in lines: stripped = line.strip() if stripped.startswith("ORDERED_OBJS") and "+=" in stripped: parts = stripped.split("\\") first_part = parts[0] idx = first_part.find("+=") tail = first_part[idx+2:].strip() if tail: objs_lines.append(tail) collecting = True if len(parts) > 1: for p in parts[1:]: p = p.strip() if p: objs_lines.append(p) continue if collecting: if stripped.endswith("\\"): objs_lines.append(stripped[:-1].strip()) else: objs_lines.append(stripped) collecting = False objs_str = ' '.join(objs_lines) objs_str = re.sub(r"\$\([^)]+\)", "", objs_str) objs = [] for part in objs_str.split(): part = part.strip() if part.startswith('"') and part.endswith('"'): part = part[1:-1] if part: objs.append(part) c_files = [] include_dirs = set() for obj_path in objs: if "DebugTools" in obj_path: continue if "v120" in obj_path: continue if obj_path.startswith("Debug\\") or obj_path.startswith("Debug/"): rel_path = obj_path.replace("Debug\\", "Src\\").replace("Debug/", "Src/") else: rel_path = obj_path abs_path = os.path.normpath(os.path.join(project_root, rel_path)) root, ext = os.path.splitext(abs_path) if ext.lower() == ".obj": c_path = root + ".c" else: c_path = abs_path # Сохраняем только .c файлы if c_path.lower().endswith(".c"): c_files.append(c_path) dir_path = os.path.dirname(c_path) if dir_path and "DebugTools" not in dir_path: include_dirs.add(dir_path) return c_files, sorted(include_dirs) # Шаблон для поиска глобальных переменных # Пример: int varname; # Пропускаем строки с static и функции VAR_PATTERN = re.compile( r'^\s*(?!static)(\w[\w\s\*]+)\s+(\w+)\s*(=\s*[^;]+)?\s*;', re.MULTILINE) EXTERN_PATTERN = re.compile( r'^\s*extern\s+[\w\s\*]+\s+(\w+)\s*;', re.MULTILINE) TYPEDEF_PATTERN = re.compile( r'typedef\s+(struct|union)?\s*(\w+)?\s*{[^}]*}\s*(\w+)\s*;', re.DOTALL) TYPEDEF_SIMPLE_PATTERN = re.compile( r'typedef\s+(.+?)\s+(\w+)\s*;', re.DOTALL) def map_type_to_pt(typename, varname): typename = typename.strip() # Убираем const и volatile, где бы они ни были (например, "const volatile int") for qualifier in ('const', 'volatile'): typename = typename.replace(qualifier, '') typename = typename.strip() # снова убрать лишние пробелы после удаления # Раскрутка через typedef resolved_type = typedef_aliases.get(typename, typename) # Прямая проверка if resolved_type in type_map: return type_map[resolved_type] if resolved_type.startswith('struct'): return type_map['struct'] if resolved_type.startswith('union'): return type_map['union'] if '_iq' in resolved_type and '_iqx' in type_map: return type_map['_iqx'] return 'pt_unknown' def get_iq_define(vtype): if '_iq' in vtype: # Преобразуем _iqXX в t_iqXX return 't' + vtype[vtype.index('_iq'):] else: return 't_iq_none' def get_files_by_ext(roots, exts): files = [] for root in roots: for dirpath, _, filenames in os.walk(root): for f in filenames: if any(f.endswith(e) for e in exts): files.append(os.path.join(dirpath, f)) return files def read_file_try_encodings(filepath): for enc in ['utf-8', 'cp1251']: try: with open(filepath, 'r', encoding=enc) as f: content = f.read() content = strip_single_line_comments(content) # <=== ВАЖНО return content, enc except UnicodeDecodeError: continue raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251") FUNC_PATTERN = re.compile( r'\w[\w\s\*\(\),]*\([^;{)]*\)\s*\{(?:[^{}]*|\{[^}]*\})*?\}', re.DOTALL) def strip_single_line_comments(code): # Удалим // ... до конца строки return re.sub(r'//.*?$', '', code, flags=re.MULTILINE) def remove_function_bodies(code): result = [] i = 0 length = len(code) while i < length: match = re.search(r'\b[\w\s\*\(\),]*\([^;{}]*\)\s*\{', code[i:]) if not match: result.append(code[i:]) break start = i + match.start() brace_start = i + match.end() - 1 result.append(code[i:start]) # Добавляем всё до функции # Ищем конец функции по уровню вложенности скобок brace_level = 1 j = brace_start + 1 in_string = False while j < length and brace_level > 0: char = code[j] if char == '"' or char == "'": quote = char j += 1 while j < length and code[j] != quote: if code[j] == '\\': j += 2 else: j += 1 elif code[j] == '{': brace_level += 1 elif code[j] == '}': brace_level -= 1 j += 1 # Заменяем тело функции пробелами той же длины result.append(' ' * (j - start)) i = j return ''.join(result) def extract_struct_definitions_from_file(filepath): with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() # Удаляем комментарии content = re.sub(r'//.*', '', content) content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL) type_definitions = {} anonymous_counter = [0] def split_fields(body): """ Разбивает тело struct/union на поля с учётом вложенных { }. Возвращает список строк - полей (без ;). """ fields = [] start = 0 brace_level = 0 for i, ch in enumerate(body): if ch == '{': brace_level += 1 elif ch == '}': brace_level -= 1 elif ch == ';' and brace_level == 0: fields.append(body[start:i].strip()) start = i + 1 # если что-то осталось после последнего ; tail = body[start:].strip() if tail: fields.append(tail) return fields def parse_fields(body): fields = {} body = body.strip('{} \n\t') field_strings = split_fields(body) for field_str in field_strings: if field_str.startswith(('struct ', 'union ')): # Вложенный struct/union # Пытаемся найти имя и тело m = re.match(r'(struct|union)\s*(\w*)\s*({.*})\s*(\w+)?', field_str, re.DOTALL) if m: kind, tag, inner_body, varname = m.groups() if not varname: # Анонимная вложенная структура/объединение varname = f"__anon_{anonymous_counter[0]}" anonymous_counter[0] += 1 type_definitions[varname] = parse_fields(inner_body) fields[varname] = varname else: # Если есть имя переменной anon_type_name = f"__anon_{anonymous_counter[0]}" anonymous_counter[0] += 1 type_definitions[anon_type_name] = parse_fields(inner_body) fields[varname] = anon_type_name if tag == '' else f"{kind} {tag}" else: # Не смогли распарсить вложенную структуру - кладём как есть fields[field_str] = None else: # Обычное поле # Нужно выделить тип и имя поля с учётом указателей и массивов m = re.match(r'(.+?)\s+([\w\*\[\]]+)$', field_str) if m: typename, varname = m.groups() fields[varname.strip()] = typename.strip() else: # не распарсили поле — кладём "как есть" fields[field_str] = None return fields # Парсим typedef struct/union {...} Alias; typedef_struct_pattern = re.compile( r'\btypedef\s+(struct|union)\s*({.*?})\s*(\w+)\s*;', re.DOTALL) for match in typedef_struct_pattern.finditer(content): alias = match.group(3) body = match.group(2) type_definitions[alias] = parse_fields(body) # Парсим struct/union Name {...}; named_struct_pattern = re.compile( r'\b(struct|union)\s+(\w+)\s*({.*?})\s*;', re.DOTALL) for match in named_struct_pattern.finditer(content): name = match.group(2) body = match.group(3) if name not in type_definitions: type_definitions[name] = parse_fields(body) return type_definitions def parse_vars_from_file(filepath): content, encoding = read_file_try_encodings(filepath) content_clean = remove_function_bodies(content) vars_found = [] for m in VAR_PATTERN.finditer(content_clean): typename = m.group(1).strip() varlist = m.group(2) for var in varlist.split(','): varname = var.strip() # Убираем указатели и массивы varname = varname.strip('*').split('[')[0] # Фильтрация мусора if not re.match(r'^[_a-zA-Z][_a-zA-Z0-9]*$', varname): continue vars_found.append((varname, typename)) return vars_found, encoding def parse_typedefs_from_file(filepath): """ Парсит typedef из файла C: - typedef struct/union { ... } Alias; - typedef simple_type Alias; Возвращает словарь alias -> базовый тип (например, 'MyType' -> 'struct' или 'unsigned int'). """ with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() # Убираем однострочные комментарии content = re.sub(r'//.*?$', '', content, flags=re.MULTILINE) # Убираем многострочные комментарии content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL) aliases = {} # --- Парсим typedef struct/union {...} Alias; # Используем стек для вложенных фигурных скобок typedef_struct_union_pattern = re.compile(r'\btypedef\s+(struct|union)\b', re.IGNORECASE) pos = 0 while True: m = typedef_struct_union_pattern.search(content, pos) if not m: break kind = m.group(1) brace_open_pos = content.find('{', m.end()) if brace_open_pos == -1: # Нет тела структуры, пропускаем pos = m.end() continue # Ищем позицию закрывающей скобки с учётом вложенности brace_level = 1 i = brace_open_pos + 1 while i < len(content) and brace_level > 0: if content[i] == '{': brace_level += 1 elif content[i] == '}': brace_level -= 1 i += 1 if brace_level != 0: # Некорректный синтаксис pos = m.end() continue # Отрезок typedef структуры/объединения typedef_block = content[m.start():i] # После закрывающей скобки ожидаем имя алиаса и точку с запятой rest = content[i:].lstrip() alias_match = re.match(r'(\w+)\s*;', rest) if alias_match: alias_name = alias_match.group(1) aliases[alias_name] = kind # например, "struct" или "union" pos = i + alias_match.end() else: # Анонимный typedef? Просто пропускаем pos = i # --- Удаляем typedef struct/union {...} Alias; чтобы не мешали простым typedef # Для этого удалим весь блок typedef struct/union {...} Alias; def remove_typedef_struct_union_blocks(text): result = [] last_pos = 0 for m in typedef_struct_union_pattern.finditer(text): brace_open_pos = text.find('{', m.end()) if brace_open_pos == -1: continue brace_level = 1 i = brace_open_pos + 1 while i < len(text) and brace_level > 0: if text[i] == '{': brace_level += 1 elif text[i] == '}': brace_level -= 1 i += 1 if brace_level != 0: continue # Ищем имя алиаса и точку с запятой после i rest = text[i:].lstrip() alias_match = re.match(r'\w+\s*;', rest) if alias_match: end_pos = i + alias_match.end() result.append(text[last_pos:m.start()]) last_pos = end_pos result.append(text[last_pos:]) return ''.join(result) content_simple = remove_typedef_struct_union_blocks(content) # --- Парсим простые typedef: typedef base_type alias; simple_typedef_pattern = re.compile( r'\btypedef\s+([^{};]+?)\s+(\w+)\s*;', re.MULTILINE) for m in simple_typedef_pattern.finditer(content_simple): base_type = m.group(1).strip() alias = m.group(2).strip() if alias not in aliases: aliases[alias] = base_type return aliases def parse_externs_from_file(filepath): content, encoding = read_file_try_encodings(filepath) extern_vars = set(EXTERN_PATTERN.findall(content)) return extern_vars, encoding def get_relpath_to_srcdirs(filepath, src_dirs): # Ищем первый SRC_DIR, в котором лежит filepath, и возвращаем относительный путь for d in src_dirs: try: rel = os.path.relpath(filepath, d) # Проверим, что rel не уходит выше корня (например, не начинается с '..') if not rel.startswith('..'): return rel.replace('\\', '/') # Для единообразия в путях except ValueError: continue # Если ни один SRC_DIR не подходит, вернуть basename return os.path.basename(filepath) def find_all_includes_recursive(c_files, include_dirs, processed_files=None): """ Рекурсивно ищет все include-файлы начиная с заданных c_files. include_dirs — список директорий, в которых ищем include-файлы. processed_files — множество уже обработанных файлов (для избежания циклов). """ if processed_files is None: processed_files = set() include_files = set() include_pattern = re.compile(r'#include\s+"([^"]+)"') for cfile in c_files: norm_path = os.path.normpath(cfile) if norm_path in processed_files: continue processed_files.add(norm_path) content, _ = read_file_try_encodings(cfile) includes = include_pattern.findall(content) for inc in includes: include_files.add(inc) # Ищем полный путь к include-файлу в include_dirs inc_full_path = None for dir_ in include_dirs: candidate = os.path.normpath(os.path.join(dir_, inc)) if os.path.isfile(candidate): inc_full_path = candidate break # Если нашли include-файл и ещё не обработали — рекурсивно ищем include внутри него if inc_full_path and inc_full_path not in processed_files: nested_includes = find_all_includes_recursive( [inc_full_path], include_dirs, processed_files ) include_files.update(nested_includes) return include_files def file_uses_typedef_vars(filepath, missing_vars, typedefs): """ Проверяем, содержит ли файл typedef с одним из missing_vars. typedefs — словарь alias->базовый тип, полученный parse_typedefs_from_file. """ # Здесь проще проверить, есть ли в typedefs ключи из missing_vars, # но в условии — typedef переменных из missing_in_h, # значит, нужно проверить typedef переменных с этими именами # Для упрощения — прочитаем содержимое и проверим наличие typedef с именами из missing_vars content, _ = read_file_try_encodings(filepath) for var in missing_vars: # Ищем в content что-то типа typedef ... var ...; # Для простоты регулярка: typedef ... var; pattern = re.compile(r'\btypedef\b[^;]*\b' + re.escape(var) + r'\b[^;]*;', re.DOTALL) if pattern.search(content): return True return False def file_contains_extern_vars(filepath, extern_vars): content, _ = read_file_try_encodings(filepath) for var in extern_vars: pattern = re.compile(r'\bextern\b[^;]*\b' + re.escape(var) + r'\b\s*;') if pattern.search(content): return True return False def add_struct_fields(new_debug_vars, var_prefix, struct_type, all_structs, existing_debug_vars): """ Рекурсивно добавляет поля структуры в new_debug_vars. var_prefix: имя переменной или путь к полю (например "myVar" или "myVar.subfield") struct_type: имя типа структуры (например "MyStruct") all_structs: словарь всех структур existing_debug_vars: множество уже существующих имен переменных """ if struct_type not in all_structs: # Типа нет в структуре, значит не структура или неизвестный тип — выходим return fields = all_structs[struct_type] for field_name, field_type in fields.items(): full_var_name = f"{var_prefix}.{field_name}" if full_var_name in existing_debug_vars or full_var_name in new_debug_vars: continue if field_type is None: continue iq_type = get_iq_define(field_type) pt_type = map_type_to_pt(field_type, full_var_name) formated_name = f'"{full_var_name}"' line = f'\t{{(char *)&{full_var_name:<40} , {pt_type:<20} , {iq_type:<20} , {formated_name:<40}}}, \\' new_debug_vars[full_var_name] = line # Если поле — тоже структура, рекурсивно раскрываем # При этом убираем указатели и массивы из типа, если они есть base_field_type = field_type.split()[0] # например "struct" или "MyStruct*" # Удаляем указатели и массивы из имени типа для поиска в all_structs base_field_type = re.sub(r'[\*\[\]0-9]+', '', base_field_type) base_field_type = base_field_type.strip() if base_field_type in all_structs: add_struct_fields(new_debug_vars, full_var_name, base_field_type, all_structs, existing_debug_vars) else: a=1 def main(make_path): c_files, include_dirs = parse_makefile(make_path) all_dirs = c_files + include_dirs h_files = get_files_by_ext(include_dirs, ['.h']) vars_in_c = {} encodings_c = set() for cf in c_files: vars_found, enc = parse_vars_from_file(cf) encodings_c.add(enc) for vname, vtype in vars_found: vars_in_c[vname] = (vtype, cf) externs_in_h = set() for hf in h_files: externs, _ = parse_externs_from_file(hf) externs_in_h |= externs missing_in_h = {v: vars_in_c[v] for v in vars_in_c if v not in externs_in_h} all_structs = {} for fl in c_files + h_files: structs = extract_struct_definitions_from_file(fl) all_structs.update(structs) # Подготовка typedef-ов global typedef_aliases typedef_aliases = {} for f in h_files + c_files: aliases = parse_typedefs_from_file(f) typedef_aliases.update(aliases) included_headers = find_all_includes_recursive(c_files, include_dirs) # все подключенные .h include_files = [] for header in included_headers: # Полный путь к файлу нужно получить full_path = None for d in all_dirs: candidate = os.path.join(d, header) if os.path.isfile(candidate): full_path = candidate break if not full_path: continue # файл не найден в SRC_DIRS — игнорируем # Проверяем, что это строго .h if not full_path.endswith('.h'): continue # Парсим typedef из файла typedefs = parse_typedefs_from_file(full_path) # Проверяем, использует ли typedef переменные из missing_in_h по их vtype uses_typedef = any(vtype in typedefs for (vtype, path) in missing_in_h.values()) # Проверяем наличие extern переменных has_extern = file_contains_extern_vars(full_path, externs_in_h) if not has_extern and not uses_typedef: continue # Если прошло оба условия — добавляем include_files.append(full_path) # Путь к debug_vars.h common_prefix = os.path.commonpath(include_dirs) output_dir = os.path.join(common_prefix, 'DebugTools') os.makedirs(output_dir, exist_ok=True) output_path = os.path.join(output_dir, 'debug_vars.c') # Считываем существующие переменные existing_debug_vars = {} if os.path.isfile(output_path): with open(output_path, 'r', encoding='utf-8', errors='ignore') as f: old_lines = f.readlines() for line in old_lines: m = re.match(r'\s*{.*?,\s+.*?,\s+.*?,\s+"([_a-zA-Z][_a-zA-Z0-9]*)"\s*},', line) if m: varname = m.group(1) existing_debug_vars[varname] = line.strip() # Генерируем новые переменные new_debug_vars = {} for vname, (vtype, path) in vars_in_c.items(): if vname in existing_debug_vars: continue iq_type = get_iq_define(vtype) pt_type = map_type_to_pt(vtype, vname) if pt_type not in ('pt_struct', 'pt_union'): formated_name = f'"{vname}"' line = f'{{(char *)&{vname:<41} , {pt_type:<21} , {iq_type:<21} , {formated_name:<42}}}, \\' 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) # Сортируем новые переменные по алфавиту по имени sorted_new_debug_vars = dict(sorted(new_debug_vars.items())) # Объединяем все переменные all_debug_lines = list(existing_debug_vars.values()) + list(sorted_new_debug_vars.values()) # DebugVar_Numb теперь по всем переменным out_lines = [] out_lines.append("// Этот файл сгенерирован автоматически") out_lines.append(f'#include "debug_tools.h"') out_lines.append('\n\n// Инклюды для доступа к переменным') out_lines.append(f'#include "IQmathLib.h"') for incf in include_files: out_lines.append(f'#include "{incf}"') out_lines.append('\n\n// Экстерны для доступа к переменным') for vname, (vtype, path) in missing_in_h.items(): out_lines.append(f'extern {vtype} {vname};') out_lines.append(f'\n\n// Определение массива с указателями на переменные для отладки') out_lines.append(f'int DebugVar_Numb = {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)}') if __name__ == '__main__': if len(sys.argv) < 2: main('F:/Work/Projects/TMS/TMS_new_bus/Debug/makefile') print("Usage: parse_makefile.py path/to/Makefile") sys.exit(1) else: main(sys.argv[1])