#!/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 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 ") 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()