Files
qt6-migration-pipeline/fix_export_headers_v6.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

321 lines
11 KiB
Python

#!/usr/bin/env python3
"""
Export Header Fix Script v6
Fixes wrong export header includes in Qt6 migrated code.
Handles:
- Files in include/ directories that are referenced without include/ prefix in CMakeLists.txt
- STATIC vs SHARED library distinction
- Multiple targets in same CMakeLists.txt
"""
import os
import re
import sys
from pathlib import Path
def parse_cmakelists(cmakelists_path):
"""Parse CMakeLists.txt and extract library info with their source files"""
libraries = []
with open(cmakelists_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# Remove comments
content = re.sub(r'#.*', '', content)
# Find all SET() variables
set_vars = {}
for match in re.finditer(r'SET\s*\(\s*(\w+)\s+([^)]+)\)', content, re.IGNORECASE | re.DOTALL):
var_name = match.group(1)
var_value = match.group(2).strip()
# Split by whitespace or newlines
files = [f.strip() for f in re.split(r'\s+', var_value) if f.strip()]
set_vars[var_name] = files
# Find NGL_ADD_LIBRARY and NGL_ADD_LIBRARY2 calls
for match in re.finditer(r'NGL_ADD_LIBRARY2?\s*\(([^)]+)\)', content, re.IGNORECASE | re.DOTALL):
args = match.group(1).strip()
tokens = args.split()
if len(tokens) < 3:
continue
# Parse: NGL_ADD_LIBRARY(targets... target_name TYPE ${SRCS})
# or: NGL_ADD_LIBRARY(target_name TYPE ${SRCS})
# Find TYPE keyword (SHARED, STATIC, MODULE, OBJECT)
type_idx = -1
for i, token in enumerate(tokens):
if token.upper() in ['SHARED', 'STATIC', 'MODULE', 'OBJECT']:
type_idx = i
break
if type_idx == -1:
continue
lib_type = tokens[type_idx].upper()
# Target name is right before TYPE (could be multiple targets like WS AAR SV HLA saaTelegrams)
# But usually it's just one target name
# Look for the token right before TYPE
if type_idx > 0:
target_name = tokens[type_idx - 1]
# Make sure it's not another keyword
if target_name.upper() in ['SHARED', 'STATIC', 'MODULE', 'OBJECT']:
continue
else:
continue
# Get source files (everything after TYPE)
src_tokens = tokens[type_idx + 1:]
all_files = []
for token in src_tokens:
if token.startswith('${'):
# Variable reference
var_name = token[2:-1] # Remove ${ and }
if var_name in set_vars:
all_files.extend(set_vars[var_name])
else:
all_files.append(token)
libraries.append({
'name': target_name,
'type': lib_type,
'files': all_files,
'cmakelists': cmakelists_path
})
return libraries
def find_all_cmakelists(base_dir):
"""Find all CMakeLists.txt files"""
return list(base_dir.rglob('CMakeLists.txt'))
def map_files_to_targets(base_dir):
"""Map header files to their targets by searching all CMakeLists.txt"""
file_to_target = {}
cmakelists_files = find_all_cmakelists(base_dir)
print(f"Found {len(cmakelists_files)} CMakeLists.txt files")
# First pass: collect all targets and their source files
all_targets = []
for cmakelists in cmakelists_files:
libraries = parse_cmakelists(cmakelists)
cmakelists_dir = cmakelists.parent
for lib in libraries:
lib['cmakelists_dir'] = cmakelists_dir
all_targets.append(lib)
# Second pass: map files to targets
for lib in all_targets:
cmakelists_dir = lib['cmakelists_dir']
for src_file in lib['files']:
src_filename = Path(src_file).name
# Try direct path first
src_path = cmakelists_dir / src_file
if src_path.exists():
file_to_target[str(src_path)] = (lib['name'], lib['type'])
# Search in same directory tree
for header_file in cmakelists_dir.rglob(src_filename):
if header_file.suffix in ['.h', '.hpp']:
file_to_target[str(header_file)] = (lib['name'], lib['type'])
# Search in include/ subdirectory relative to CMakeLists.txt
include_dir = cmakelists_dir / 'include'
if include_dir.exists():
for header_file in include_dir.rglob(src_filename):
if header_file.suffix in ['.h', '.hpp']:
file_to_target[str(header_file)] = (lib['name'], lib['type'])
# Third pass: handle global include/ directory (e.g., include/AdlerJson/)
# Match based on plugin directory name (parent of CMakeLists.txt)
global_include = base_dir / 'include'
if global_include.exists():
for lib in all_targets:
cmakelists_dir = lib['cmakelists_dir']
plugin_name = cmakelists_dir.name # e.g., AdlerJson
# Check if there's an include/<PluginName>/ directory
target_include_dir = global_include / plugin_name
if target_include_dir.exists():
for header_file in target_include_dir.rglob('*.h'):
file_to_target[str(header_file)] = (lib['name'], lib['type'])
for header_file in target_include_dir.rglob('*.hpp'):
file_to_target[str(header_file)] = (lib['name'], lib['type'])
# Filter out external library directories (RTI, ArcGIS, GeographicLib)
# These are external dependencies without their own CMakeLists.txt in the project
external_lib_dirs = ['include/rti', 'include/ArcGIS', 'include/GeographicLib']
files_to_remove = []
for file_path in file_to_target.keys():
for ext_dir in external_lib_dirs:
if ext_dir in file_path:
files_to_remove.append(file_path)
break
for file_path in files_to_remove:
del file_to_target[file_path]
print(f"Mapped {len(file_to_target)} files to targets (after filtering external libs)")
return file_to_target
def get_export_header_name(target_name):
"""Generate export header name from target name"""
return f"{target_name.lower()}_export.h"
def fix_export_header(file_path, target_name, lib_type):
"""Fix export header include in a file"""
export_header = get_export_header_name(target_name)
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Find existing export header includes
patterns = [
r'#include\s*<\w+_export\.h>',
r'#include\s*"\w+_export\.h"',
r'#include\s*<\w+/\w+_export\.h>',
r'#include\s*"\w+/\w+_export\.h"'
]
found_export = False
for pattern in patterns:
if re.search(pattern, content):
found_export = True
break
if not found_export:
return False, "No export header found"
# Replace with correct export header
new_content = re.sub(
r'#include\s*[<"]\w+/?\w*_export\.h[>"]',
f'#include "{export_header}"',
content
)
if new_content == content:
return False, "No changes made"
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
return True, f"Fixed to {export_header}"
def remove_export_header(file_path):
"""Remove export header include from STATIC library file"""
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
patterns = [
r'#include\s*[<"]\w+/?\w*_export\.h[>"]\n'
]
original = content
for pattern in patterns:
content = re.sub(pattern, '', content)
if content == original:
return False, "No export header found to remove"
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
return True, "Removed export header (STATIC library)"
def main():
if len(sys.argv) < 2:
print("Usage: fix_export_headers_v6.py <base_dir>")
sys.exit(1)
base_dir = Path(sys.argv[1]).resolve()
# Map all files to targets
file_to_target = map_files_to_targets(base_dir)
# Find all header files
header_files = list(base_dir.rglob('*.h')) + list(base_dir.rglob('*.hpp'))
fix_count = 0
remove_count = 0
skip_count = 0
error_count = 0
error_files = []
for header_path in header_files:
header_str = str(header_path)
# Skip build directories
if '/build' in header_str or 'build2/' in header_str or 'build-' in header_str:
skip_count += 1
continue
# Skip external code
if 'grantlee/' in header_str.lower():
skip_count += 1
continue
# Skip external library directories (RTI, ArcGIS, GeographicLib)
# These are external dependencies without their own CMakeLists.txt in the project
if '/include/rti/' in header_str or '/include/ArcGIS/' in header_str or '/include/GeographicLib/' in header_str:
skip_count += 1
continue
# Check if file has export header include FIRST
with open(header_path, 'r', encoding='utf-8') as f:
content = f.read()
has_export = bool(re.search(r'#include\s*[<"].*_export\.h[>"]', content))
if not has_export:
skip_count += 1
continue
# Now check if file is mapped to a target (only for files with export headers)
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]
# Handle based on library type
if lib_type == 'STATIC':
success, msg = remove_export_header(header_path)
if success:
remove_count += 1
print(f"REMOVED (STATIC): {header_path} - {msg}")
else:
skip_count += 1
else:
# SHARED or MODULE
success, msg = fix_export_header(header_path, target_name, lib_type)
if success:
fix_count += 1
print(f"FIXED: {header_path} -> {msg}")
else:
skip_count += 1
print("\n" + "="*80)
print("Summary:")
print(f" Fixed: {fix_count}")
print(f" Removed (STATIC): {remove_count}")
print(f" Skipped: {skip_count}")
print(f" Errors: {error_count}")
print("="*80)
if error_files and error_count > 0:
print("\nErrors:")
for path, reason in error_files[:20]:
print(f" ERROR: {path} - {reason}")
if error_count > 20:
print(f" ... and {error_count - 20} more")
if __name__ == '__main__':
main()