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

336 lines
11 KiB
Python

#!/usr/bin/env python3
"""
Export Header Fix Script v7
Fixes wrong export header includes in Qt6 migrated code.
Improvements in v7:
- Better directory-based mapping for headers not explicitly in CMakeLists.txt
- Handles subdirectory structures like Lts/Common/LtsWidgets/
- Handles test executables (remove export headers)
"""
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
# 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
if type_idx > 0:
target_name = tokens[type_idx - 1]
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('${'):
var_name = token[2:-1]
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
})
# Also find ADD_EXECUTABLE calls (for test files)
for match in re.finditer(r'ADD_EXECUTABLE\s*\(\s*(\w+)\s+([^)]+)\)', content, re.IGNORECASE | re.DOTALL):
exe_name = match.group(1)
files_str = match.group(2).strip()
files = [f.strip() for f in re.split(r'\s+', files_str) if f.strip()]
libraries.append({
'name': exe_name,
'type': 'EXECUTABLE',
'files': 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"""
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 directories
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 using multiple strategies
# Strategy 1: Direct file reference in CMakeLists.txt
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'])
# Strategy 2: Directory-based mapping
# If a header is in a directory with a CMakeLists.txt that defines a library,
# assume it belongs to that library
for lib in all_targets:
cmakelists_dir = lib['cmakelists_dir']
# Map all .h/.hpp files in the CMakeLists directory and subdirectories
for header_file in cmakelists_dir.rglob('*.h'):
if str(header_file) not in file_to_target:
file_to_target[str(header_file)] = (lib['name'], lib['type'])
for header_file in cmakelists_dir.rglob('*.hpp'):
if str(header_file) not in file_to_target:
file_to_target[str(header_file)] = (lib['name'], lib['type'])
# Strategy 3: Global include/ directory mapping by plugin name
global_include = base_dir / 'include'
if global_include.exists():
for lib in all_targets:
cmakelists_dir = lib['cmakelists_dir']
plugin_name = cmakelists_dir.name
target_include_dir = global_include / plugin_name
if target_include_dir.exists():
for header_file in target_include_dir.rglob('*.h'):
if str(header_file) not in file_to_target:
file_to_target[str(header_file)] = (lib['name'], lib['type'])
for header_file in target_include_dir.rglob('*.hpp'):
if str(header_file) not in file_to_target:
file_to_target[str(header_file)] = (lib['name'], lib['type'])
# Filter out external library directories
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 or executable 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"
def main():
if len(sys.argv) < 2:
print("Usage: fix_export_headers_v7.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
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
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' or lib_type == 'EXECUTABLE':
success, msg = remove_export_header(header_path)
if success:
remove_count += 1
print(f"REMOVED ({lib_type}): {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/EXEC): {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[:30]:
print(f" ERROR: {path} - {reason}")
if error_count > 30:
print(f" ... and {error_count - 30} more")
if __name__ == '__main__':
main()