Files
qt6-migration-pipeline/fix_export_headers_v5.py
T
torstenandCopilot 342ffd3e5c Update submodule to latest BUILD_RTI fixes
- HandlerMinelayerData: BUILD_RTI Guards für RTI-Abhängigkeiten
- svcTabCommon: Export-Header-Fix via Fallback-Target
- TrainingAreaBase: Stub-Header für BUILD_RTI=OFF
- ddsfts: Include-Pfad und Dependency-Fixes
- BUILD_RTI=OFF Compilation vollständig unterstützt

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 01M0JNZA586SACX1PRZ2DJAE5S
2026-08-21 19:30:20 +02:00

356 lines
13 KiB
Python

#!/usr/bin/env python3
"""
Export Header Fix Script v5
Repariert falsche Export-Header-Includes in Qt6-migrierten Dateien.
WICHTIG:
- Nur SHARED/MODULE Libraries bekommen Export-Header
- STATIC Libraries brauchen KEINE Export-Header
- Export-Header aus Build-Verzeichnis benötigen "..." (doppelte Anführungszeichen)
- Nur Files im gleichen Verzeichnis wie CMakeLists.txt (oder Subverzeichnissen) werden verarbeitet
"""
import os
import re
import sys
from pathlib import Path
def parse_cmakelists(cmakelists_path):
"""
Parse CMakeLists.txt und extrahiere:
- SET() Variablen mit ihren Files
- NGL_ADD_LIBRARY() Aufrufe mit Target-Name, Typ und Sources-Variable
"""
with open(cmakelists_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# SET() Variablen extrahieren: SET(VARNAME file1.cpp file2.h ...)
set_vars = {}
set_pattern = r'SET\s*\(\s*(SRCS\w*)\s*((?:[^)]+))\)'
for match in re.finditer(set_pattern, content, re.IGNORECASE):
var_name = match.group(1).strip()
# Files extrahieren (alles nach VARNAME bis zum schließenden Parenthesis)
files_str = match.group(2).strip()
# Neueinlines und multiple spaces zu single space
files_str = ' '.join(files_str.split())
# Files extrahieren (Wörter die mit . enden oder .extension haben)
files = re.findall(r'[\w\.\-_/]+', files_str)
# Filtere nur Header/Source Files
files = [f for f in files if any(f.endswith(ext) for ext in ['.h', '.hpp', '.cpp', '.cc', '.cxx'])]
set_vars[var_name] = files
# NGL_ADD_LIBRARY() Aufrufe extrahieren
# Pattern: NGL_ADD_LIBRARY(... target_name SHARED|STATIC|MODULE ${SRCS_var} ...)
libraries = []
# Multi-line Pattern für NGL_ADD_LIBRARY
lib_pattern = r'NGL_ADD_LIBRARY\s*\(([^)]+)\)'
for match in re.finditer(lib_pattern, content, re.IGNORECASE | re.DOTALL):
args_str = match.group(1).strip()
args = ' '.join(args_str.split()) # Whitespace normalisieren
# Target-Typ finden (SHARED, STATIC, MODULE)
lib_type = None
if re.search(r'\bSHARED\b', args, re.IGNORECASE):
lib_type = 'SHARED'
elif re.search(r'\bMODULE\b', args, re.IGNORECASE):
lib_type = 'MODULE'
elif re.search(r'\bSTATIC\b', args, re.IGNORECASE):
lib_type = 'STATIC'
if not lib_type:
continue
# Target-Namen finden (nach den Platform-Flags wie AAR, SV, WS, HLA, ALL)
# Pattern: NGL_ADD_LIBRARY(platforms... target_name TYPE ...)
tokens = args.split()
target_name = None
src_var = None
# Nach Platform-Flags suchen und dann den nächsten Token als Target-Name nehmen
platform_flags = {'AAR', 'SV', 'WS', 'HLA', 'ALL', 'WIN', 'LINUX'}
found_platform = False
for i, token in enumerate(tokens):
token_upper = token.upper()
if token_upper in platform_flags:
found_platform = True
continue
if found_platform and target_name is None:
# Nächster Token nach Platform-Flags ist der Target-Name
if token_upper not in {'SHARED', 'STATIC', 'MODULE'}:
target_name = token
continue
if target_name and token_upper in {'SHARED', 'STATIC', 'MODULE'}:
# Nach Target-Name und Typ suchen wir nach ${SRCS_var}
for j in range(i+1, min(i+5, len(tokens))):
if tokens[j].startswith('${') and tokens[j].endswith('}'):
var_name = tokens[j][2:-1] # ${VAR} -> VAR
if var_name in set_vars:
src_var = var_name
break
break
if target_name:
libraries.append({
'name': target_name,
'type': lib_type,
'src_var': src_var,
'files': set_vars.get(src_var, []) if src_var else []
})
return libraries
def find_cmakelists_for_file(header_path, base_dir):
"""Finde CMakeLists.txt und prüfe alle Parent-Verzeichnisse"""
# Suche aufsteigend nach CMakeLists.txt
current = header_path.parent
while current != base_dir and current != base_dir.parent:
cmakelists = current / 'CMakeLists.txt'
if cmakelists.exists():
return cmakelists
current = current.parent
return None
def search_file_in_cmakelists(header_path, cmakelists):
"""Suche ob ein File in einem CMakeLists.txt referenziert wird"""
cmakelists_dir = cmakelists.parent
# Versuche relativen Pfad vom CMakeLists.txt Verzeichnis
try:
rel_path = header_path.relative_to(cmakelists_dir)
rel_path_str = str(rel_path)
file_name = header_path.name
except ValueError:
return None, None
libraries = parse_cmakelists(cmakelists)
for lib in libraries:
# Prüfe ob File in den Sources ist
for src_file in lib['files']:
if src_file == rel_path_str or src_file == file_name:
return lib['name'], lib['type']
return None, None
def get_export_header_name(target_name):
"""Generiere Export-Header Name aus Target-Name"""
return f"{target_name.lower()}_export.h"
def fix_export_header_in_file(header_path, target_name, lib_type):
"""
Repariere Export-Header Include in einer Datei.
Return: (fixed, old_include, new_include) oder (False, None, None)
"""
with open(header_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# Finde bestehende Export-Header Includes
# Pattern: #include "xxx_export.h" oder #include <xxx_export.h>
include_pattern = r'#\s*include\s*[<"]([^>"\s]+_export\.h)[>"]'
matches = list(re.finditer(include_pattern, content))
if not matches:
return False, None, None
expected_export = get_export_header_name(target_name)
# Prüfe alle Export-Header Includes
fixed = False
old_include = None
new_include = None
for match in matches:
current_export = match.group(1)
# Wenn bereits korrekt, skip
if current_export == expected_export:
# Prüfe ob es "..." statt <...> verwendet (für Build-Verzeichnis)
full_match = match.group(0)
if full_match.startswith('#include <'):
# Falsche Syntax, sollte "..." sein
new_include = f'#include "{expected_export}"'
old_include = full_match
content = content[:match.start()] + new_include + content[match.end():]
fixed = True
continue
# Falscher Export-Header
old_include = f'#include "{current_export}"' if f'#include "{current_export}"' in content else f'#include <{current_export}>'
new_include = f'#include "{expected_export}"'
content = content.replace(old_include, new_include, 1)
fixed = True
break
if fixed:
with open(header_path, 'w', encoding='utf-8') as f:
f.write(content)
return fixed, old_include, new_include
def remove_export_header_from_static(header_path):
"""Entferne Export-Header Include aus STATIC Library File"""
with open(header_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# Finde Export-Header Includes
include_pattern = r'#\s*include\s*[<"]([^>"\s]+_export\.h)[>"]'
matches = list(re.finditer(include_pattern, content))
if not matches:
return False, None
old_include = matches[0].group(0)
content = content.replace(old_include, '', 1)
# Doppelte Leerzeilen entfernen
content = re.sub(r'\n\s*\n\s*\n', '\n\n', content)
with open(header_path, 'w', encoding='utf-8') as f:
f.write(content)
return True, old_include
def main():
if len(sys.argv) != 2:
print("Usage: fix_export_headers_v5.py <base_dir>")
sys.exit(1)
base_dir = Path(sys.argv[1]).resolve()
# Finde alle CMakeLists.txt Dateien
cmakelists_files = list(base_dir.rglob('CMakeLists.txt'))
print(f"Found {len(cmakelists_files)} CMakeLists.txt files")
# Parse alle CMakeLists.txt und baue Mapping: file_path -> (target_name, lib_type)
file_to_target = {}
for cmakelists in cmakelists_files:
cmakelists_dir = cmakelists.parent
libraries = parse_cmakelists(cmakelists)
for lib in libraries:
# Nur SHARED/MODULE brauchen Export-Header
if lib['type'] not in ('SHARED', 'MODULE'):
# STATIC Libraries: Files sollten KEINEN Export-Header haben
for src_file in lib['files']:
file_path = cmakelists_dir / src_file
if file_path.exists():
file_to_target[str(file_path)] = (lib['name'], 'STATIC')
continue
# SHARED/MODULE: Files brauchen Export-Header
for src_file in lib['files']:
file_path = cmakelists_dir / src_file
if file_path.exists():
file_to_target[str(file_path)] = (lib['name'], lib['type'])
print(f"Mapped {len(file_to_target)} files to targets")
# Finde alle Header-Dateien
header_files = list(base_dir.rglob('*.h')) + list(base_dir.rglob('*.hpp'))
print(f"Checking {len(header_files)} header files...")
fixed_count = 0
skipped_count = 0
error_count = 0
removed_count = 0
fixed_files = []
removed_files = []
error_files = []
for header_path in header_files:
header_str = str(header_path)
# Überspringe Files in include/grantlee/ (externer Code)
if 'include/grantlee' in header_str:
skipped_count += 1
continue
# Überspringe export.h Files selbst
if header_path.name.endswith('_export.h'):
skipped_count += 1
continue
if header_str not in file_to_target:
# File ist keinem Target zugeordnet - könnte in include/ sein
# Durchsuche alle CMakeLists.txt Dateien in Parent-Verzeichnissen
cmakelists_list = find_cmakelists_for_file(header_path, base_dir)
# Wenn kein CMakeLists.txt im Parent-Pfad, durchsuche alle
if not cmakelists_list:
# Suche in allen CMakeLists.txt nach diesem File
for cmakelists in cmakelists_files:
target_name, lib_type = search_file_in_cmakelists(header_path, cmakelists)
if target_name:
file_to_target[header_str] = (target_name, lib_type)
break
else:
# CMakeLists.txt gefunden, prüfe ob File dort referenziert ist
target_name, lib_type = search_file_in_cmakelists(header_path, cmakelists_list)
if target_name:
file_to_target[header_str] = (target_name, lib_type)
if header_str not in file_to_target:
error_count += 1
error_files.append((header_path, "Could not determine target"))
continue
target_name, lib_type = file_to_target[header_str]
if lib_type == 'STATIC':
# STATIC Library - Export-Header entfernen
fixed, old_include = remove_export_header_from_static(header_path)
if fixed:
removed_count += 1
removed_files.append((header_path, old_include, f"Target '{target_name}' is STATIC"))
else:
# SHARED/MODULE Library - Export-Header korrigieren
fixed, old_include, new_include = fix_export_header_in_file(header_path, target_name, lib_type)
if fixed:
fixed_count += 1
fixed_files.append((header_path, old_include, new_include, f"Target '{target_name}' ({lib_type})"))
else:
skipped_count += 1
# Ausgabe
print("\n" + "="*80)
print("Summary:")
print(f" Fixed: {fixed_count}")
print(f" Removed (STATIC): {removed_count}")
print(f" Skipped: {skipped_count}")
print(f" Errors: {error_count}")
print("="*80)
if fixed_files:
print("\nFixed files:")
for header_path, old_inc, new_inc, info in fixed_files[:20]:
rel_path = header_path.relative_to(base_dir)
print(f" FIX: {rel_path}")
print(f" {old_inc} -> {new_inc}")
print(f" ({info})")
if len(fixed_files) > 20:
print(f" ... and {len(fixed_files) - 20} more")
if removed_files:
print("\nRemoved export headers from STATIC libraries:")
for header_path, old_inc, info in removed_files[:20]:
rel_path = header_path.relative_to(base_dir)
print(f" REMOVE: {rel_path}")
print(f" {old_inc}")
print(f" ({info})")
if len(removed_files) > 20:
print(f" ... and {len(removed_files) - 20} more")
if error_files:
print("\nErrors:")
for header_path, error in error_files[:20]:
rel_path = header_path.relative_to(base_dir)
print(f" ERROR: {rel_path} - {error}")
if len(error_files) > 20:
print(f" ... and {len(error_files) - 20} more")
if __name__ == '__main__':
main()