debugVarTool/generateVars.py

374 lines
14 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 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 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 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')
# Генерируем новые переменные
vars, includes, externs = read_vars_from_xml(proj_path, xml_path)
# Сортируем новые переменные по алфавиту по имени
sorted_new_debug_vars = dict(sorted(vars.items()))
# Считываем существующие переменные
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, info in vars.items():
vtype = info["type"]
is_extern = info["extern"]
is_static = info.get("static", False)
if is_static:
continue # пропускаем static переменные
# Можно добавить проверку enable — если есть и False, пропускаем переменную
if "enable" in info and info["enable"] is False:
continue
path = info["file"]
if vname in existing_debug_vars:
continue
iq_type = get_iq_define(vtype)
pt_type = map_type_to_pt(vtype, vname)
# Дополнительные поля, например комментарий
comment = info.get("comment", "")
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} , {formated_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 = [str(v) for v in existing_debug_vars.values()] + [str(v) for v in 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()