debugVarTool/.out/setupVars_out.py
Razvalyaev 4517087194 улучшены коллбеки для считывания xml
улучшен парс структур и юнионов переменных
улучшен поиск
2025-07-09 05:44:03 +03:00

581 lines
24 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.

import sys
import os
import subprocess
import xml.etree.ElementTree as ET
from generateVars import map_type_to_pt, get_iq_define, type_map
from enum import IntEnum
from scanVars import run_scan
from generateVars import run_generate
from PySide6.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem,
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit
)
from PySide6.QtGui import QTextCursor
from PySide6.QtCore import Qt, QProcess
class rows(IntEnum):
include = 0
name = 1
type = 2
pt_type = 3
iq_type = 4
ret_type = 5
short_name = 6
# 1. Парсим vars.xml
def make_absolute_path(path, base_path):
if not os.path.isabs(path):
return os.path.abspath(os.path.join(base_path, path))
return os.path.abspath(path)
def parse_vars(filename):
tree = ET.parse(filename)
root = tree.getroot()
vars_list = []
variables_elem = root.find('variables')
if variables_elem is not None:
for var in variables_elem.findall('var'):
name = var.attrib.get('name', '')
vars_list.append({
'name': name,
'enable': var.findtext('enable', 'false'),
'shortname': var.findtext('shortname', name),
'pt_type': var.findtext('pt_type', 'pt_unknown'),
'iq_type': var.findtext('iq_type', 'iq_none'),
'return_type': var.findtext('return_type', 'int'),
'type': var.findtext('type', 'unknown'),
'file': var.findtext('file', ''),
'extern': var.findtext('extern', 'false') == 'true',
'static': var.findtext('static', 'false') == 'true',
})
return vars_list
# 2. Парсим structSup.xml
def parse_structs(filename):
tree = ET.parse(filename)
root = tree.getroot()
structs = {}
typedef_map = {}
# --- Считываем структуры ---
structs_elem = root.find('structs')
if structs_elem is not None:
for struct in structs_elem.findall('struct'):
name = struct.attrib['name']
fields = {}
for field in struct.findall('field'):
fname = field.attrib.get('name')
ftype = field.attrib.get('type')
if fname and ftype:
fields[fname] = ftype
structs[name] = fields
# --- Считываем typedef-ы ---
typedefs_elem = root.find('typedefs')
if typedefs_elem is not None:
for typedef in typedefs_elem.findall('typedef'):
name = typedef.attrib.get('name')
target_type = typedef.attrib.get('type')
if name and target_type:
typedef_map[name.strip()] = target_type.strip()
return structs, typedef_map
class ProcessOutputWindow(QWidget):
def __init__(self, command, args):
super().__init__()
self.setWindowTitle("Поиск переменных...")
self.resize(600, 400)
self.layout = QVBoxLayout(self)
self.output_edit = QTextEdit()
self.output_edit.setReadOnly(True)
self.layout.addWidget(self.output_edit)
self.btn_close = QPushButton("Закрыть")
self.btn_close.setEnabled(False)
self.btn_close.clicked.connect(self.close)
self.layout.addWidget(self.btn_close)
self.process = QProcess(self)
self.process.setProcessChannelMode(QProcess.MergedChannels)
self.process.readyReadStandardOutput.connect(self.handle_stdout)
self.process.finished.connect(self.process_finished)
self.process.start(command, args)
def handle_stdout(self):
data = self.process.readAllStandardOutput()
text = data.data().decode('utf-8')
self.output_edit.append(text)
def process_finished(self):
self.output_edit.append("\n--- Процесс завершён ---")
self.btn_close.setEnabled(True)
# 3. UI: таблица с переменными
class VarEditor(QWidget):
def __init__(self):
super().__init__()
self.vars_list = []
self.structs = {}
self.typedef_map = {}
self.structs_xml_path = None # сюда запишем путь из <structs_xml>
self.initUI()
def initUI(self):
self.setWindowTitle("Variable Editor")
# --- Поля ввода пути проекта и XML ---
# XML Output
xml_layout = QHBoxLayout()
xml_layout.addWidget(QLabel("XML Output:"))
self.xml_output_edit = QLineEdit()
xml_layout.addWidget(self.xml_output_edit)
self.xml_output_edit.returnPressed.connect(self.read_xml_file)
btn_xml_browse = QPushButton("...")
btn_xml_browse.setFixedWidth(30)
xml_layout.addWidget(btn_xml_browse)
btn_xml_browse.clicked.connect(self.browse_xml_output)
# Project Path
proj_layout = QHBoxLayout()
proj_layout.addWidget(QLabel("Project Path:"))
self.proj_path_edit = QLineEdit()
proj_layout.addWidget(self.proj_path_edit)
btn_proj_browse = QPushButton("...")
btn_proj_browse.setFixedWidth(30)
proj_layout.addWidget(btn_proj_browse)
btn_proj_browse.clicked.connect(self.browse_proj_path)
# Makefile Path
makefile_layout = QHBoxLayout()
makefile_layout.addWidget(QLabel("Makefile Path (relative path):"))
self.makefile_edit = QLineEdit()
makefile_layout.addWidget(self.makefile_edit)
btn_makefile_browse = QPushButton("...")
btn_makefile_browse.setFixedWidth(30)
makefile_layout.addWidget(btn_makefile_browse)
btn_makefile_browse.clicked.connect(self.browse_makefile)
# Source Output File/Directory
source_output_layout = QHBoxLayout()
source_output_layout.addWidget(QLabel("Source Output File:"))
self.source_output_edit = QLineEdit()
source_output_layout.addWidget(self.source_output_edit)
btn_source_output_browse = QPushButton("...")
btn_source_output_browse.setFixedWidth(30)
source_output_layout.addWidget(btn_source_output_browse)
btn_source_output_browse.clicked.connect(self.browse_source_output)
self.btn_update_vars = QPushButton("Обновить данные о переменных")
self.btn_update_vars.clicked.connect(self.update_vars_data)
# Таблица переменных
self.table = QTableWidget(len(self.vars_list), 7)
self.table.setHorizontalHeaderLabels([
'Include',
'Name',
'Origin Type',
'Pointer Type',
'IQ Type',
'Return Type',
'Short Name'
])
self.table.setEditTriggers(QAbstractItemView.AllEditTriggers)
# Кнопка сохранения
btn_save = QPushButton("Build")
btn_save.clicked.connect(self.save_build)
# Основной layout
layout = QVBoxLayout()
layout.addLayout(xml_layout)
layout.addLayout(proj_layout)
layout.addLayout(makefile_layout)
layout.addWidget(self.btn_update_vars)
layout.addWidget(self.table)
layout.addWidget(btn_save)
layout.addLayout(source_output_layout)
self.setLayout(layout)
def update_vars_data(self):
proj_path = os.path.abspath(self.proj_path_edit.text().strip())
xml_path = os.path.abspath(self.xml_output_edit.text().strip())
makefile_path = make_absolute_path(self.makefile_edit.text().strip(), proj_path)
if not proj_path or not xml_path:
QMessageBox.warning(self, "Ошибка", "Пожалуйста, укажите пути проекта и XML.")
return
if not os.path.isfile(makefile_path):
QMessageBox.warning(self, "Ошибка", f"Makefile не найден по пути:\n{makefile_path}")
return
scanvars_exe = "scanVars.exe"
args = [proj_path, makefile_path, xml_path]
# Создаем и показываем окно с выводом процесса
self.proc_win = ProcessOutputWindow("python", [])
self.proc_win.show()
self.proc_win.output_edit.append("Запуск анализа переменных...")
# Запускаем в отдельном процессе через QProcess, если нужен live-вывод:
from threading import Thread
def run_scan_wrapper():
try:
run_scan(proj_path, makefile_path, xml_path)
self.proc_win.output_edit.append("\n--- Анализ завершён ---")
except Exception as e:
self.proc_win.output_edit.append(f"\n[ОШИБКА] {e}")
self.proc_win.btn_close.setEnabled(True)
self.after_scanvars_finished(0, 0)
Thread(target=run_scan_wrapper).start()
# Можно подписаться на сигнал завершения процесса, если хочешь обновить UI после
#self.proc_win.process.finished.connect(lambda exitCode, exitStatus: self.after_scanvars_finished(exitCode, exitStatus))
def save_build(self):
vars_out = []
for row in range(self.table.rowCount()):
include_cb = self.table.cellWidget(row, rows.include)
if not include_cb.isChecked():
continue
name_edit = self.table.cellWidget(row, rows.name)
pt_type_combo = self.table.cellWidget(row, rows.pt_type)
iq_combo = self.table.cellWidget(row, rows.iq_type)
ret_combo = self.table.cellWidget(row, rows.ret_type)
short_name_edit = self.table.cellWidget(row, rows.short_name)
var_data = {
'name': name_edit.text(),
'type': 'pt_' + pt_type_combo.currentText(),
'iq_type': iq_combo.currentText(),
'return_type': ret_combo.currentText() if ret_combo.currentText() else 'int',
'short_name': short_name_edit.text(),
}
vars_out.append(var_data)
# Здесь нужно указать абсолютные пути к проекту, xml и output (замени на свои)
proj_path = os.path.abspath(self.proj_path_edit.text().strip())
xml_path = os.path.abspath(self.xml_output_edit.text().strip())
output_dir_c_file = os.path.abspath(self.source_output_edit.text().strip())
if not proj_path or not xml_path or not output_dir_c_file:
QMessageBox.warning(self, "Ошибка", "Заполните все пути: проект, XML и output.")
return
try:
run_generate(proj_path, xml_path, output_dir_c_file)
QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.")
except Exception as e:
QMessageBox.critical(self, "Ошибка при генерации", str(e))
def browse_proj_path(self):
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку проекта")
if dir_path:
self.proj_path_edit.setText(dir_path)
def read_xml_file(self):
file_path = self.xml_output_edit.text().strip()
if file_path and not os.path.isfile(file_path):
return
self.vars_list = parse_vars(file_path)
try:
tree = ET.parse(file_path)
root = tree.getroot()
proj_path = self.proj_path_edit.text().strip()
if not proj_path:
# Если в поле ничего нет, пробуем взять из XML
proj_path_from_xml = root.attrib.get('proj_path', '').strip()
if proj_path_from_xml and os.path.isdir(proj_path_from_xml):
proj_path = proj_path_from_xml
self.proj_path_edit.setText(proj_path_from_xml)
else:
QMessageBox.warning(
self,
"Внимание",
"Путь к проекту (proj_path) не найден или не существует.\n"
"Пожалуйста, укажите его вручную в поле 'Project Path'."
)
else:
if not os.path.isdir(proj_path):
QMessageBox.warning(
self,
"Внимание",
f"Указанный путь к проекту не существует:\n{proj_path}\n"
"Пожалуйста, исправьте путь в поле 'Project Path'."
)
# --- makefile_path из атрибута ---
makefile_path = root.attrib.get('makefile_path', '').strip()
makefile_path_full = make_absolute_path(makefile_path, proj_path)
if makefile_path_full and os.path.isfile(makefile_path_full):
self.makefile_edit.setText(makefile_path)
# --- structs_path из атрибута ---
structs_path = root.attrib.get('structs_path', '').strip()
structs_path_full = make_absolute_path(structs_path, proj_path)
if structs_path_full and os.path.isfile(structs_path_full):
self.structs_xml_path = structs_path_full
self.structs, self.typedef_map = parse_structs(structs_path_full)
else:
self.structs_xml_path = None
self.update_table()
except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
def browse_xml_output(self):
file_path, _ = QFileDialog.getSaveFileName(
self,
"Выберите XML файл",
filter="XML files (*.xml);;All Files (*)"
)
self.xml_output_edit.setText(file_path)
self.read_xml_file()
def browse_xml_struct(self):
file_path, _ = QFileDialog.getSaveFileName(self, "Выберите XML файл", filter="XML files (*.xml);;All Files (*)")
if file_path:
self.xml_output_edit.setText(file_path)
if os.path.isfile(file_path):
self.structs, self.typedef_map = parse_structs(file_path)
def browse_makefile(self):
file_path, _ = QFileDialog.getOpenFileName(
self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)"
)
if file_path:
self.makefile_edit.setText(file_path)
def browse_source_output(self):
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c")
if dir_path:
self.source_output_edit.setText(dir_path)
def after_scanvars_finished(self, exitCode, exitStatus):
xml_path = self.xml_output_edit.text().strip()
if not os.path.isfile(xml_path):
QMessageBox.critical(self, "Ошибка", f"Файл не найден: {xml_path}")
return
try:
# Читаем структуры, если задан путь
if self.structs_xml_path and os.path.isfile(self.structs_xml_path):
try:
self.structs, self.typedef_map = parse_structs(self.structs_xml_path)
# При необходимости обновите UI или сделайте что-то с self.structs
except Exception as e:
QMessageBox.warning(self, "Внимание", f"Не удалось загрузить структуры из {self.structs_xml_path}:\n{e}")
self.vars_list = parse_vars(xml_path)
self.update_table()
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}")
def update_table(self):
self.type_options = list(dict.fromkeys(type_map.values()))
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
self.table.setRowCount(len(self.vars_list))
for row, var in enumerate(self.vars_list):
cb = QCheckBox()
enable_str = var.get('enable', 'false')
cb.setChecked(enable_str.lower() == 'true')
self.table.setCellWidget(row, rows.include, cb)
name_edit = QLineEdit(var['name'])
if var['type'] in self.structs:
completer = QCompleter(self.structs[var['type']].keys())
completer.setCaseSensitivity(Qt.CaseInsensitive)
name_edit.setCompleter(completer)
self.table.setCellWidget(row, rows.name, name_edit)
# Type (origin)
origin_type = var.get('type', '').strip()
origin_item = QTableWidgetItem(origin_type)
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # read-only
self.table.setItem(row, rows.type, origin_item)
pt_type_combo = QComboBox()
pt_type_combo.addItems(self.display_type_options)
internal_type = map_type_to_pt(var['type'], var['name'], self.typedef_map)
display_type = internal_type.replace('pt_', '')
if display_type in self.display_type_options:
pt_type_combo.setCurrentText(display_type)
else:
pt_type_combo.addItem(display_type)
pt_type_combo.setCurrentText(display_type)
self.table.setCellWidget(row, rows.pt_type, pt_type_combo)
iq_combo = QComboBox()
iq_combo.addItems(iq_types)
iq_type = get_iq_define(var['type']) # Получаем IQ-тип, например 'iq24'
display_type = iq_type.replace('t_', '')
if iq_type in iq_types:
iq_combo.setCurrentText(display_type)
else:
iq_combo.addItem(display_type)
iq_combo.setCurrentText(display_type)
self.table.setCellWidget(row, rows.iq_type, iq_combo)
ret_combo = QComboBox()
ret_combo.addItems(iq_types)
self.table.setCellWidget(row, rows.ret_type, ret_combo)
short_name_edit = QLineEdit(var['name'])
self.table.setCellWidget(row, rows.short_name, short_name_edit)
cb.stateChanged.connect(self.save_table_to_xml)
name_edit.textChanged.connect(self.save_table_to_xml)
pt_type_combo.currentTextChanged.connect(self.save_table_to_xml)
iq_combo.currentTextChanged.connect(self.save_table_to_xml)
ret_combo.currentTextChanged.connect(self.save_table_to_xml)
short_name_edit.textChanged.connect(self.save_table_to_xml)
def save_table_to_xml(self):
def make_relative_path(abs_path, base_path):
try:
return os.path.relpath(abs_path, base_path).replace("\\", "/")
except ValueError:
return abs_path.replace("\\", "/")
xml_path = self.xml_output_edit.text().strip()
proj_path = self.proj_path_edit.text().strip()
makefile_path = self.makefile_edit.text().strip()
if not xml_path or not os.path.isfile(xml_path):
print("XML файл не найден или путь пустой")
return
try:
tree = ET.parse(xml_path)
root = tree.getroot()
# Обновим атрибуты с относительными путями
if os.path.isdir(proj_path):
root.set("proj_path", proj_path.replace("\\", "/"))
if os.path.isfile(makefile_path):
rel_makefile = make_relative_path(makefile_path, proj_path)
root.set("makefile_path", rel_makefile)
if self.structs_xml_path and os.path.isfile(self.structs_xml_path):
rel_struct_path = make_relative_path(self.structs_xml_path, proj_path)
root.set("structs_path", rel_struct_path)
vars_elem = root.find('variables')
if vars_elem is None:
# Если блока нет, создаём
vars_elem = ET.SubElement(root, 'variables')
original_info = {}
for var_elem in vars_elem.findall('var'):
name = var_elem.attrib.get('name')
if name:
original_info[name] = {
'type': var_elem.findtext('type', ''),
'file': var_elem.findtext('file', ''),
'extern': var_elem.findtext('extern', ''),
'static': var_elem.findtext('static', '')
}
# Собираем данные из таблицы
updated_vars = []
for row in range(self.table.rowCount()):
cb = self.table.cellWidget(row, 0)
name_edit = self.table.cellWidget(row, 1)
pt_type_combo = self.table.cellWidget(row, 3)
iq_combo = self.table.cellWidget(row, 4)
ret_combo = self.table.cellWidget(row, 5)
short_name_edit = self.table.cellWidget(row, 6)
var_name = name_edit.text()
# Берём оригинальные type и file из словаря, если есть
orig_type = original_info.get(var_name, {}).get('type', '')
orig_file = original_info.get(var_name, {}).get('file', '')
orig_extern = original_info.get(var_name, {}).get('extern', '')
orig_static = original_info.get(var_name, {}).get('static', '')
updated_vars.append({
'name': var_name,
'enable': cb.isChecked(),
'shortname': short_name_edit.text(),
'pt_type': 'pt_' + pt_type_combo.currentText(),
'iq_type': iq_combo.currentText(),
'return_type': ret_combo.currentText() or 'int',
'type': orig_type,
'file': orig_file,
'extern': orig_extern,
'static': orig_static,
})
# Обновляем или добавляем по одному var в XML
for v in updated_vars:
var_elem = None
for ve in vars_elem.findall('var'):
if ve.attrib.get('name') == v['name']:
var_elem = ve
break
if var_elem is None:
var_elem = ET.SubElement(vars_elem, 'var', {'name': v['name']})
def set_sub_elem_text(parent, tag, text):
el = parent.find(tag)
if el is None:
el = ET.SubElement(parent, tag)
el.text = str(text)
set_sub_elem_text(var_elem, 'enable', 'true' if v['enable'] else 'false')
set_sub_elem_text(var_elem, 'shortname', v['shortname'])
set_sub_elem_text(var_elem, 'pt_type', v['pt_type'])
set_sub_elem_text(var_elem, 'iq_type', v['iq_type'])
set_sub_elem_text(var_elem, 'return_type', v['return_type'])
set_sub_elem_text(var_elem, 'type', v['type'])
set_sub_elem_text(var_elem, 'file', v['file'])
set_sub_elem_text(var_elem, 'extern', v['extern'])
set_sub_elem_text(var_elem, 'static', v['static'])
# Сохраняем изменения
tree.write(xml_path, encoding='utf-8', xml_declaration=True)
except Exception as e:
print(f"Ошибка при сохранении XML: {e}")
if __name__ == "__main__":
app = QApplication(sys.argv)
editor = VarEditor()
editor.resize(900, 600)
editor.show()
sys.exit(app.exec())