733 lines
28 KiB
Python
733 lines
28 KiB
Python
# 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])
|