debugVarTool/Src/generate_debug_vars.py
Razvalyaev 043359fe66 + фикс кривых проверок на наличие uint8_t
+ фикс перевод float в iq
+ фикс поиска вручнуб добавленных переменных в debug_vars.c
2025-07-17 10:37:58 +03:00

687 lines
25 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# build command
# pyinstaller --onefile --distpath . --workpath ./build --specpath ./build generate_debug_vars.py
# start script
# generate_debug_vars.exe F:\Work\Projects\TMS\TMS_new_bus\ Src/DebugTools/vars.xml Src/DebugTools
import sys
import os
import re
import lxml.etree as ET
from pathlib import Path
from xml.dom import minidom
import myXML
import argparse
shortnameSize = 10
# === Словарь соответствия типов XML → DebugVarType_t ===
type_map_tms = 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'),
])
# === Словарь соответствия типов XML → DebugVarType_t ===
type_map_stm32 = dict([
*[(k, 'pt_int8') for k in (
'int8_t', 'signed char', 'char'
)],
# --- 8-bit unsigned ---
*[(k, 'pt_uint8') for k in (
'uint8_t', 'unsigned char'
)],
# --- 16-bit signed ---
*[(k, 'pt_int16') for k in (
'int16_t', 'short', 'short int', 'signed short', 'signed short int'
)],
# --- 16-bit unsigned ---
*[(k, 'pt_uint16') for k in (
'uint16_t', 'unsigned short', 'unsigned short int'
)],
# --- 32-bit signed ---
*[(k, 'pt_int32') for k in (
'int32_t', 'int', 'signed', 'signed int'
)],
# --- 32-bit unsigned ---
*[(k, 'pt_uint32') for k in (
'uint32_t', 'unsigned', 'unsigned int'
)],
# --- 64-bit signed ---
*[(k, 'pt_int64') for k in (
'int64_t', 'long long', 'signed long long', 'signed long long int'
)],
# --- 64-bit unsigned ---
*[(k, 'pt_uint64') for k in (
'uint64_t', 'unsigned long long', 'unsigned long long int'
)],
# --- Float ---
*[(k, 'pt_float') for k in (
'float', 'float32_t'
)],
# --- Struct and Union ---
('struct', 'pt_struct'),
('union', 'pt_union'),
('struct*', 'pt_ptr_struct'),
('union*', 'pt_ptr_union'),
('struct[]', 'pt_arr_struct'),
('union[]', 'pt_arr_union'),
# === POINTERS ===
# 8-bit
*[(k, 'pt_ptr_int8') for k in (
'int8_t*', 'signed char*', 'char*'
)],
*[(k, 'pt_ptr_uint8') for k in (
'uint8_t*', 'unsigned char*'
)],
# 16-bit
*[(k, 'pt_ptr_int16') for k in (
'int16_t*', 'short*', 'short int*', 'signed short*', 'signed short int*'
)],
*[(k, 'pt_ptr_uint16') for k in (
'uint16_t*', 'unsigned short*', 'unsigned short int*'
)],
# 32-bit
*[(k, 'pt_ptr_int32') for k in (
'int32_t*', 'int*', 'signed*', 'signed int*'
)],
*[(k, 'pt_ptr_uint32') for k in (
'uint32_t*', 'unsigned*', 'unsigned int*'
)],
# 64-bit
*[(k, 'pt_ptr_int64') for k in (
'int64_t*', 'long long*', 'signed long long*', 'signed long long int*'
)],
*[(k, 'pt_ptr_uint64') for k in (
'uint64_t*', 'unsigned long long*', 'unsigned long long int*'
)],
# float*
*[(k, 'pt_ptr_float') for k in (
'float*', 'float32_t*'
)],
# === ARRAYS ===
# 8-bit
*[(k, 'pt_arr_int8') for k in (
'int8_t[]', 'signed char[]', 'char[]'
)],
*[(k, 'pt_arr_uint8') for k in (
'uint8_t[]', 'unsigned char[]'
)],
# 16-bit
*[(k, 'pt_arr_int16') for k in (
'int16_t[]', 'short[]', 'short int[]', 'signed short[]', 'signed short int[]'
)],
*[(k, 'pt_arr_uint16') for k in (
'uint16_t[]', 'unsigned short[]', 'unsigned short int[]'
)],
# 32-bit
*[(k, 'pt_arr_int32') for k in (
'int32_t[]', 'int[]', 'signed[]', 'signed int[]'
)],
*[(k, 'pt_arr_uint32') for k in (
'uint32_t[]', 'unsigned[]', 'unsigned int[]'
)],
# 64-bit
*[(k, 'pt_arr_int64') for k in (
'int64_t[]', 'long long[]', 'signed long long[]', 'signed long long int[]'
)],
*[(k, 'pt_arr_uint64') for k in (
'uint64_t[]', 'unsigned long long[]', 'unsigned long long int[]'
)],
# float[]
*[(k, 'pt_arr_float') for k in (
'float[]', 'float32_t[]'
)],
])
type_map = type_map_tms
stm_flag_global = 0
def choose_type_map(stm_flag):
global type_map # объявляем, что будем менять глобальную переменную
global stm_flag_global # объявляем, что будем менять глобальную переменную
if stm_flag:
type_map = type_map_stm32
stm_flag_global = 1
else:
type_map = type_map_tms
stm_flag_global = 0
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 (в <variables>), добавляем её и сохраняем XML-файл.
Возвращает True если что-то добавлено и XML перезаписан, иначе False.
"""
pattern = re.compile(
r'{\s*\(uint8_t\s*\*\)\s*&([a-zA-Z_][a-zA-Z0-9_]*(?:\[.*?\])?(?:(?:\.|->)[a-zA-Z_][a-zA-Z0-9_]*(?:\[.*?\])?)*)\s*,\s*'
r'(pt_\w+)\s*,\s*'
r'(t?_?iq\w+)\s*,\s*'
r'(t?_?iq\w+)\s*,\s*'
r'"([^"]+)"'
)
# Считываем существующие переменные
parsed_vars = {}
if os.path.isfile(output_path):
with open(output_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
# {(uint8_t *)&some.deep.var.name , pt_uint16 , t_iq15 , t_iq10, "ShortName"},
m = pattern.search(line)
if m:
full_varname = m.group(1) # e.g., some.deep.var.name
pt_type = m.group(2)
iq_type = m.group(3)
return_type = m.group(4)
shortname = m.group(5)
parsed_vars[full_varname] = {
'pt_type': pt_type,
'iq_type': iq_type,
'enable': True,
'show_var': True,
'shortname': shortname,
'return_type': return_type,
'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:
myXML.fwrite(root, xml_full_path)
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 = {}
# Читаем переменные из <variables>
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 переменные из <externs>
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 read_file_try_encodings(filepath):
if not os.path.isfile(filepath):
# Файл не существует — просто вернуть пустую строку или None
return "", None
for enc in ['utf-8', 'cp1251']:
try:
with open(filepath, 'r', encoding=enc) as f:
content = f.read()
return content, enc
except UnicodeDecodeError:
continue
raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251")
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')
LIBC_path = os.path.join(output_dir, 'debug_tools.c')
LIBH_path = os.path.join(output_dir, 'debug_tools.h')
# Запись новых переменных для в 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)
ret_type = info.get('return_type')
if not ret_type:
pt_type = 't_iq_none'
# Дополнительные поля, например комментарий
comment = info.get("comment", "")
short_name = info.get("shortname", f'"{vname}"')
short_trimmed = short_name[:shortnameSize] # ограничиваем длину до 10
if pt_type not in ('pt_struct', 'pt_union'):
f_name = f'{vname},'
f_type = f'{pt_type},'
f_iq = f'{iq_type},'
f_ret_iq = f'{ret_type},'
f_short_name = f'"{short_trimmed}"' # оборачиваем в кавычки
# Добавим комментарий после записи, если он есть
comment_str = f' // {comment}' if comment else ''
line = f'{{(uint8_t *)&{f_name:<58} {f_type:<15} {f_iq:<15} {f_ret_iq:<15} {f_short_name:<21}}}, \\{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)};')
if stm_flag_global == 0:
out_lines.append('#pragma DATA_SECTION(dbg_vars,".dbgvar_info")')
out_lines.append('// pointer_type iq_type return_iq_type short_name')
out_lines.append('DebugVar_t dbg_vars[] = {\\')
out_lines.extend(all_debug_lines)
out_lines.append('};')
out_lines.append('')
# Выберем кодировку для записи файла
# Если встречается несколько, возьмем первую из set
if stm_flag_global == 0:
enc_to_write = 'cp1251'
else:
enc_to_write = 'utf-8'
#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))
if os.path.isfile(LIBC_path):
libc_code, _ = read_file_try_encodings(LIBC_path)
with open(LIBC_path, 'w', encoding=enc_to_write) as f:
f.write(libc_code)
if os.path.isfile(LIBH_path):
libh_code, _ = read_file_try_encodings(LIBH_path)
with open(LIBH_path, 'w', encoding=enc_to_write) as f:
f.write(libh_code)
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, 0)
if __name__ == "__main__":
main()
def run_generate(proj_path, xml_path, output_dir, shortname_size):
import os
global shortnameSize
shortnameSize = shortname_size
# 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)