374 lines
14 KiB
Python
374 lines
14 KiB
Python
# 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() |