на win7 работает и Nuitka и PyInstaller

добавлен прогрессбар на сканирование переменных
исправлены мелкие баги
This commit is contained in:
2025-07-10 13:49:13 +03:00
parent cd6645df98
commit 858e7de57d
138 changed files with 19617 additions and 385024 deletions

View File

@@ -13,6 +13,7 @@ from generateVars import run_generate
from setupVars import *
from VariableSelector import *
from VariableTable import VariableTableWidget, rows
from scanVarGUI import *
from PySide2.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem,
@@ -24,61 +25,6 @@ from PySide2.QtGui import QTextCursor, QKeyEvent
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QSettings
class EmittingStream(QObject):
text_written = Signal(str)
def __init__(self):
super().__init__()
self._buffer = ""
def write(self, text):
self._buffer += text
while '\n' in self._buffer:
line, self._buffer = self._buffer.split('\n', 1)
# Отправляем строку без '\n'
self.text_written.emit(line)
def flush(self):
if self._buffer:
self.text_written.emit(self._buffer)
self._buffer = ""
class ProcessOutputWindowDummy(QDialog):
def __init__(self, on_done_callback):
super().__init__()
self.setWindowTitle("Поиск переменных...")
self.resize(600, 400)
self.setModal(True) # сделаем окно модальным
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.layout.addWidget(self.btn_close)
self.btn_close.clicked.connect(self.__handle_done)
self._on_done_callback = on_done_callback
def __handle_done(self):
if self._on_done_callback:
self._on_done_callback()
self.accept() # закрыть диалог
def append_text(self, text):
cursor = self.output_edit.textCursor()
cursor.movePosition(QTextCursor.End)
for line in text.splitlines():
self.output_edit.append(line)
self.output_edit.setTextCursor(cursor)
self.output_edit.ensureCursorVisible()
# 3. UI: таблица с переменными
class VarEditor(QWidget):
def __init__(self):
@@ -236,11 +182,9 @@ class VarEditor(QWidget):
# Создаём окно с кнопкой "Готово"
self.proc_win = ProcessOutputWindowDummy(self.__after_scanvars_finished)
self.emitting_stream = self.proc_win.emitting_stream # ключевая строка!
self.proc_win.show()
self.emitting_stream = EmittingStream()
self.emitting_stream.text_written.connect(self.proc_win.append_text)
def run_scan_wrapper():
try:
old_stdout = sys.stdout
@@ -562,15 +506,8 @@ class VarEditor(QWidget):
set_sub_elem_text(var_elem, 'static', static_val)
# Преобразуем дерево в строку
rough_string = ET.tostring(root, encoding="utf-8")
# Парсим и форматируем с отступами
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
# Записываем в файл
with open(self.xml_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
indent_xml(root)
ET.ElementTree(root).write(self.xml_path, encoding="utf-8", xml_declaration=True)
except Exception as e:
print(f"Ошибка при сохранении XML: {e}")

View File

@@ -458,16 +458,8 @@ class VariableSelectorDialog(QDialog):
set_text('show_var', 'false')
set_text('enable', 'false')
# Преобразуем дерево в строку
rough_string = ET.tostring(root, encoding="utf-8")
# Парсим и форматируем с отступами
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
# Записываем в файл
with open(self.xml_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
indent_xml(root)
ET.ElementTree(root).write(self.xml_path, encoding="utf-8", xml_declaration=True)
self.populate_tree()
self.accept()
@@ -517,16 +509,8 @@ class VariableSelectorDialog(QDialog):
self.all_vars[:] = [v for v in self.all_vars if v['name'] not in selected_names]
if removed_any:
# Преобразуем дерево в строку
rough_string = ET.tostring(root, encoding="utf-8")
# Парсим и форматируем с отступами
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
# Записываем в файл
with open(self.xml_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
indent_xml(root)
ET.ElementTree(root).write(self.xml_path, encoding="utf-8", xml_declaration=True)
self.populate_tree()
self.filter_tree()

Binary file not shown.

View File

@@ -9,6 +9,7 @@ import re
import xml.etree.ElementTree as ET
from pathlib import Path
from xml.dom import minidom
from scanVars import indent_xml
import argparse
@@ -215,16 +216,9 @@ def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
added_count += 1
if added_count > 0:
# Преобразуем дерево в строку
rough_string = ET.tostring(root, encoding="utf-8")
indent_xml(root)
ET.ElementTree(root).write(xml_full_path, encoding="utf-8", xml_declaration=True)
# Парсим и форматируем с отступами
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
# Записываем в файл
with open(xml_full_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
print(f"[INFO] В XML добавлено новых переменных: {added_count}")
return True
else:

BIN
Src/libclang.dll Normal file

Binary file not shown.

135
Src/scanVarGUI.py Normal file
View File

@@ -0,0 +1,135 @@
import sys
import re
import subprocess
import xml.etree.ElementTree as ET
from generateVars import type_map
from enum import IntEnum
import threading
from scanVars import run_scan
from generateVars import run_generate
from setupVars import *
from VariableSelector import *
from VariableTable import VariableTableWidget, rows
from scanVarGUI import *
from PySide2.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem,
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView, QProgressBar
)
from PySide2.QtGui import QTextCursor, QKeyEvent
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QSettings
class EmittingStream(QObject):
text_written = Signal(str)
progress_updated = Signal(str, int, int) # bar_name, current, total
def __init__(self):
super().__init__()
self._buffer = ""
self._current_bar_name = None
def write(self, text):
self._buffer += text
while '\n' in self._buffer:
line, self._buffer = self._buffer.split('\n', 1)
if line.startswith('Progress: "') and line.endswith('"'):
bar_name = self._extract_bar_name(line)
if bar_name:
self._current_bar_name = bar_name
continue # не выводим строку
elif self._is_progress_line(line):
current, total = self._extract_progress(line)
if current is not None and total is not None:
name = self._current_bar_name if self._current_bar_name else "Progress"
self.progress_updated.emit(name, current, total)
continue # не выводим строку
# если не прогресс и не имя — выводим
self.text_written.emit(line)
def _extract_bar_name(self, line):
# точно обрезаем вручную
prefix = 'Progress: "'
suffix = '"'
if line.startswith(prefix) and line.endswith(suffix):
return line[len(prefix):-1]
return None
def _is_progress_line(self, line):
return re.match(r'^Progress:\s*\d+\s*/\s*\d+$', line) is not None
def _extract_progress(self, line):
match = re.match(r'^Progress:\s*(\d+)\s*/\s*(\d+)$', line)
if match:
return int(match.group(1)), int(match.group(2))
return None, None
def flush(self):
if self._buffer:
line = self._buffer.strip()
if not self._is_progress_line(line) and not self._extract_bar_name(line):
self.text_written.emit(line)
self._buffer = ""
class ProcessOutputWindowDummy(QDialog):
def __init__(self, on_done_callback):
super().__init__()
self.setWindowTitle("Поиск переменных...")
self.resize(600, 480)
self.setModal(True)
self.layout = QVBoxLayout(self)
self.output_edit = QTextEdit()
self.output_edit.setReadOnly(True)
self.layout.addWidget(self.output_edit)
# Метка с именем прогрессбара
self.progress_label = QLabel("Progress:")
self.layout.addWidget(self.progress_label)
# Прогрессбар
self.progress_bar = QProgressBar()
self.progress_bar.setMinimum(0)
self.progress_bar.setValue(0)
self.layout.addWidget(self.progress_bar)
self.btn_close = QPushButton("Закрыть")
self.btn_close.setEnabled(False)
self.layout.addWidget(self.btn_close)
self.btn_close.clicked.connect(self.__handle_done)
self._on_done_callback = on_done_callback
self.emitting_stream = EmittingStream()
self.emitting_stream.text_written.connect(self.append_text)
self.emitting_stream.progress_updated.connect(self.update_progress)
sys.stdout = self.emitting_stream
def __handle_done(self):
sys.stdout = sys.__stdout__ # восстановить stdout
if self._on_done_callback:
self._on_done_callback()
self.accept()
def append_text(self, text):
cursor = self.output_edit.textCursor()
cursor.movePosition(QTextCursor.End)
if not text.endswith('\n'):
text += '\n'
for line in text.splitlines(True):
cursor.insertText(line)
self.output_edit.setTextCursor(cursor)
self.output_edit.ensureCursorVisible()
def update_progress(self, bar_name, current, total):
self.progress_label.setText(f"{bar_name}")
self.progress_bar.setMaximum(total)
self.progress_bar.setValue(current)

View File

@@ -15,17 +15,68 @@ from parseMakefile import parse_makefile
from collections import deque
import argparse
BITFIELD_WIDTHS = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}
def indent_xml(elem, level=0):
indent = " "
i = "\n" + level * indent
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + indent
for child in elem:
indent_xml(child, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def is_frozen():
# Для Nuitka --onefile
return getattr(sys, 'frozen', False)
# Укажи полный путь к libclang.dll — поменяй на свой путь или оставь относительный
dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "build/libclang.dll")
def get_base_path():
if is_frozen():
# В Nuitka onefile распаковывается в папку с самим exe во временной директории
return os.path.dirname(sys.executable)
else:
# Режим разработки
return os.path.dirname(os.path.abspath(__file__))
if getattr(sys, 'frozen', False): # PyInstaller/Nuitka
base_path = sys._MEIPASS if hasattr(sys, '_MEIPASS') else os.path.dirname(sys.executable)
Config.set_library_file(os.path.join(base_path, "libclang.dll"))
def print_embedded_dlls():
# Папка временной распаковки для onefile приложений Nuitka/PyInstaller
base_path = get_base_path()
print(f"Scanning DLLs in temporary folder: {base_path}")
dlls = []
for root, dirs, files in os.walk(base_path):
for file in files:
if file.lower().endswith('.dll'):
full_path = os.path.join(root, file)
rel_path = os.path.relpath(full_path, base_path)
dlls.append(rel_path)
if dlls:
print("DLL files found:")
for d in dlls:
print(" -", d)
else:
print("No DLL files found in temporary folder.")
# Вызов при старте
print_embedded_dlls()
base_path = get_base_path()
print("Base path:", base_path)
# Указываем полный путь к libclang.dll внутри распакованного каталога
dll_path = os.path.join(base_path, "libclang.dll")
if os.path.exists(dll_path):
print("Loading libclang.dll from:", dll_path)
Config.set_library_file(dll_path)
else:
# путь при разработке
Config.set_library_file("F:/Work/Projects/TMS/TMS_new_bus/Src/DebugTools/build/libclang.dll")
print("ERROR: libclang.dll not found at", dll_path)
index = cindex.Index.create()
PRINT_LEVEL = 2
@@ -165,7 +216,9 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
return vars_in_file
optional_printf(PRINT_STATUS, "Parsing header files (.h)...")
for h in h_files:
optional_printf(PRINT_STATUS, 'Progress: "Parsing variables from headers"')
total_h = len(h_files)
for i, h in enumerate(h_files, 1):
vars_in_h = parse_file(h)
for v in vars_in_h:
name = v["name"]
@@ -176,9 +229,12 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
"static": v["static"],
"file": v["file"]
}
optional_printf(PRINT_STATUS, f"Progress: {i}/{total_h}")
optional_printf(PRINT_STATUS, "Parsing source files (.c)...")
for c in c_files:
optional_printf(PRINT_STATUS, 'Progress: "Parsing variables from sources files"')
total_c = len(c_files)
for i, c in enumerate(c_files, 1):
vars_in_c = parse_file(c)
for v in vars_in_c:
name = v["name"]
@@ -196,9 +252,12 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
"static": v["static"],
"file": v["file"]
}
optional_printf(PRINT_STATUS, f"Progress: {i}/{total_c}")
optional_printf(PRINT_STATUS, "Checking which variables need explicit extern declaration...")
for name, info in unique_vars.items():
optional_printf(PRINT_STATUS, 'Progress: "Checking extern declarations"')
total_vars = len(unique_vars)
for i, (name, info) in enumerate(unique_vars.items(), 1):
if not info["extern"] and not info["static"] and info["file"].endswith('.c'):
extern_declared = False
for h in h_files_needed:
@@ -210,6 +269,7 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
"type": info["type"],
"file": info["file"]
}
optional_printf(PRINT_STATUS, f"Progress: {i}/{total_vars}")
optional_printf(PRINT_STATUS, "Analysis complete.")
optional_printf(PRINT_STATUS, f"\tTotal unique variables found: {len(unique_vars)}")
@@ -316,9 +376,21 @@ def analyze_typedefs_and_struct(typedefs, structs):
substituted_fields[fname] = resolved_type
substituted_structs[resolved_sname] = substituted_fields """
# Раскрываем typedef'ы
optional_printf(PRINT_STATUS, 'Progress: "Resolving typedefs"')
total_typedefs = len(typedefs)
resolved_typedefs = {}
for i, tname in enumerate(typedefs, 1):
resolved = resolve_typedef_rec(tname)
resolved_typedefs[tname] = resolved
optional_printf(4, f"\tTypedef {tname} resolved")
optional_printf(PRINT_STATUS, f"Progress: {i}/{total_typedefs}")
# Теперь раскрываем вложенные структуры
optional_printf(PRINT_STATUS, 'Progress: "Resolving structs"')
total_structs = len(structs)
resolved_structs = {}
for sname, fields in structs.items():
for i, (sname, fields) in enumerate(structs.items(), 1):
if "(unnamed" in sname:
optional_printf(4, f" Skipping anonymous struct/union: {sname}")
continue
@@ -327,13 +399,7 @@ def analyze_typedefs_and_struct(typedefs, structs):
resolved_fields = resolve_struct_fields(fields)
resolved_structs[sname] = resolved_fields
optional_printf(PRINT_DEBUG, f"\tStruct {sname} resolved")
# Раскрываем typedef'ы в отдельном шаге
resolved_typedefs = {}
for tname in typedefs:
resolved = resolve_typedef_rec(tname)
resolved_typedefs[tname] = resolved
optional_printf(4, f"\tTypedef {tname} resolved")
optional_printf(PRINT_STATUS, f"Progress: {i}/{total_structs}")
return resolved_typedefs, resolved_structs
@@ -441,7 +507,9 @@ def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
visit(tu.cursor)
return typedefs, structs
for c_file in c_files:
optional_printf(PRINT_STATUS, 'Progress: "Resolving structs and typedefs"')
total_files = len(c_files)
for i, c_file in enumerate(c_files, 1):
typedefs_in_file, structs_in_file = parse_file(c_file)
for name, underlying in typedefs_in_file.items():
if name not in unique_typedefs_raw:
@@ -449,6 +517,7 @@ def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
for sname, fields in structs_in_file.items():
if sname not in unique_structs_raw:
unique_structs_raw[sname] = fields
optional_printf(PRINT_STATUS, f"Progress: {i}/{total_files}")
# Теперь раскроем typedef и структуры, учитывая вложения
resolved_typedefs, resolved_structs = analyze_typedefs_and_struct(unique_typedefs_raw, unique_structs_raw)
@@ -523,9 +592,19 @@ def read_vars_from_xml(xml_path):
return vars_data
def make_relative_if_possible(path, base):
if not path:
return ''
if not os.path.isabs(path):
return path # уже относительный
try:
rel = os.path.relpath(path, base)
return rel
except ValueError as e:
print(f"[WARNING] relpath error between '{path}' and '{base}': {e}")
return path # оставляем абсолютным
def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_need_extern, structs_xml_path=None, makefile_path=None):
xml_full_path = os.path.normpath(xml_path)
# Проверяем, существует ли файл, только тогда читаем из него
@@ -579,7 +658,7 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
ET.SubElement(var_elem, "return_type").text = info.get('return_type', 'int')
ET.SubElement(var_elem, "type").text = info.get('type', 'unknown')
rel_file = os.path.relpath(info.get('file', ''), proj_path) if info.get('file') else ''
rel_file = make_relative_if_possible(info.get('file', ''), proj_path).replace("\\", "/")
ET.SubElement(var_elem, "file").text = rel_file.replace("\\", "/") if rel_file else ''
ET.SubElement(var_elem, "extern").text = str(info.get('extern', False)).lower()
ET.SubElement(var_elem, "static").text = str(info.get('static', False)).lower()
@@ -600,12 +679,8 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
ET.SubElement(var_elem, "file").text = rel_file.replace("\\", "/")
# Форматирование с отступами
rough_string = ET.tostring(root, encoding="utf-8")
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
with open(xml_full_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
indent_xml(root)
ET.ElementTree(root).write(xml_full_path, encoding="utf-8", xml_declaration=True)
optional_printf(PRINT_STATUS, f"[XML] Variables saved to {xml_full_path}")
@@ -647,12 +722,8 @@ def write_typedefs_and_structs_to_xml(proj_path, xml_path, typedefs, structs):
ET.SubElement(typedefs_elem, "typedef", name=name, type=underlying)
# Преобразуем в красиво отформатированную XML-строку
rough_string = ET.tostring(root, encoding="utf-8")
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
with open(xml_full_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
indent_xml(root)
ET.ElementTree(root).write(xml_full_path, encoding="utf-8", xml_declaration=True)
print(f"[XML] Typedefs and structs saved to: {xml_full_path}")

View File

@@ -93,16 +93,8 @@ def parse_vars(filename, typedef_map=None):
'static': var.findtext('static', 'false') == 'true',
})
# Преобразуем дерево в строку
rough_string = ET.tostring(root, encoding="utf-8")
# Парсим и форматируем с отступами
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
# Записываем в файл
with open(filename, "w", encoding="utf-8") as f:
f.write(pretty_xml)
indent_xml(root)
ET.ElementTree(root).write(filename, encoding="utf-8", xml_declaration=True)
return vars_list