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

235 lines
9.3 KiB
Python

#!/usr/bin/env python3
"""
Export Header Fixer v3 - Improved version
This script fixes incorrect export header includes in migrated Qt6 code.
It properly maps source files to their CMake targets by analyzing SRCS variables.
"""
import os
import re
import sys
from pathlib import Path
def find_cmake_files(root_dir):
"""Find all CMakeLists.txt files"""
cmake_files = []
for root, dirs, files in os.walk(root_dir):
if 'CMakeLists.txt' in files:
cmake_files.append(os.path.join(root, 'CMakeLists.txt'))
return cmake_files
def parse_cmake_file(cmake_file, root_dir=None):
"""
Parse CMakeLists.txt and extract target information.
Returns dict: {source_file: target_name}
If root_dir is provided, keys are relative paths, otherwise just filenames.
"""
target_mapping = {}
cmake_dir = os.path.dirname(cmake_file)
with open(cmake_file, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# Remove comments
content = re.sub(r'#.*', '', content)
# Join lines for multi-line commands
content = re.sub(r'\\\n', ' ', content)
# Find all NGL_ADD_LIBRARY2 calls with their SRCS variables
# Pattern: SET(<SRCS_VAR> <files...>) followed by NGL_ADD_LIBRARY2(... <TARGET> ...)
# First, find all SET commands that define source file lists
src_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)
files_str = match.group(2)
# Extract file names (remove .cpp, .h extensions for matching)
files = re.findall(r'([\w_-]+\.(?:cpp|h|cc|cxx))', files_str)
src_vars[var_name] = files
# Find all NGL_ADD_LIBRARY2 and NGL_ADD_LIBRARY calls
# Pattern 1: NGL_ADD_LIBRARY2([targets...] TARGET_NAME type ${SRCS_VAR})
lib_pattern = r'NGL_ADD_LIBRARY2?\s*\(([^)]+)\)'
for match in re.finditer(lib_pattern, content, re.IGNORECASE):
args = match.group(1).strip()
# Split into tokens
tokens = args.split()
# Find SHARED or MODULE to identify library type
lib_type_idx = -1
for i, token in enumerate(tokens):
if token.upper() in ('SHARED', 'MODULE', 'STATIC'):
lib_type_idx = i
break
if lib_type_idx < 1:
continue
# Target name is right before SHARED/MODULE
target_name = tokens[lib_type_idx - 1]
# Find SRCS variable after SHARED/MODULE
src_var = None
for i in range(lib_type_idx + 1, len(tokens)):
token = tokens[i]
if token.startswith('${') and token.endswith('}'):
src_var = token[2:-1] # Remove ${ and }
break
elif token.endswith('_SRCS') or token.endswith('_HDRS'):
src_var = token
break
# Map source files to target
if src_var and src_var in src_vars:
for src_file in src_vars[src_var]:
if root_dir:
# Store with relative path
full_path = os.path.join(cmake_dir, src_file)
rel_path = os.path.relpath(full_path, root_dir)
target_mapping[rel_path] = target_name
# Always store filename-only mapping too (will be overwritten by last occurrence)
target_mapping[src_file] = target_name
return target_mapping
def find_export_header_includes(file_path):
"""Find export header includes in a file"""
includes = []
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
for line_num, line in enumerate(f, 1):
# Match both <...> and "..." includes with _export.h
match = re.search(r'#include\s*[<"]([^>"\s]+_export\.h)[>"]', line)
if match:
includes.append((line_num, line.strip(), match.group(1)))
return includes
def get_expected_export_header(target_name):
"""Get expected export header name for a target"""
return f"{target_name.lower()}_export.h"
def fix_export_header(file_path, old_include, new_include):
"""Replace old export header include with new one"""
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# Replace the include - handle both <...> and "..."
old_pattern = re.escape(old_include)
# Match both angle brackets and quotes
content = re.sub(
rf'#include\s*[<"]{old_pattern}[>"]',
f'#include "{new_include}"',
content
)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
def main():
if len(sys.argv) < 2:
print("Usage: fix_export_headers_v3.py <directory>")
sys.exit(1)
root_dir = sys.argv[1]
print(f"Scanning directory: {root_dir}")
print("=" * 60)
# Build target mapping from all CMakeLists.txt files
cmake_files = find_cmake_files(root_dir)
print(f"Found {len(cmake_files)} CMakeLists.txt files")
target_mapping = {}
for cmake_file in cmake_files:
file_mapping = parse_cmake_file(cmake_file, root_dir)
target_mapping.update(file_mapping)
print(f"Mapped {len(target_mapping)} source files to targets")
print()
# Find all header files and check for export header includes
fixed = 0
skipped = 0
errors = 0
for root, dirs, files in os.walk(root_dir):
for file in files:
if file.endswith(('.h', '.hpp', '.hxx')):
file_path = os.path.join(root, file)
rel_path = os.path.relpath(file_path, root_dir)
# Get just the filename for mapping
filename = os.path.basename(file_path)
includes = find_export_header_includes(file_path)
for line_num, line, old_include in includes:
# Skip common header files that appear in many targets
# They should use their own target-specific export header
if filename in ('logger.h', 'settings.h', 'initialization.h'):
# These files need special handling - look only in local CMakeLists.txt
cmake_file = os.path.join(root, 'CMakeLists.txt')
if os.path.exists(cmake_file):
dir_mapping = parse_cmake_file(cmake_file, root_dir)
# Try relative path first
if rel_path in dir_mapping:
target_name = dir_mapping[rel_path]
elif filename in dir_mapping:
target_name = dir_mapping[filename]
else:
# Not found in local CMakeLists.txt - skip
skipped += 1
continue
else:
# No CMakeLists.txt in directory - skip
skipped += 1
continue
else:
# For other files, use the standard lookup
target_name = None
# Try exact relative path match first (most precise)
if rel_path in target_mapping:
target_name = target_mapping[rel_path]
# Try filename only (for unique files)
elif filename in target_mapping:
target_name = target_mapping[filename]
# Try to find by parsing local CMakeLists.txt
else:
cmake_file = os.path.join(root, 'CMakeLists.txt')
if os.path.exists(cmake_file):
dir_mapping = parse_cmake_file(cmake_file, root_dir)
# Try relative path in local mapping
if rel_path in dir_mapping:
target_name = dir_mapping[rel_path]
# Try filename in local mapping
elif filename in dir_mapping:
target_name = dir_mapping[filename]
if target_name:
expected_header = get_expected_export_header(target_name)
if old_include != expected_header:
print(f"FIX: {rel_path}")
print(f" Old: {old_include}")
print(f" New: {expected_header}")
fix_export_header(file_path, old_include, expected_header)
fixed += 1
else:
skipped += 1
else:
# File not in mapping - might be in a SRCS list we didn't parse
# or might be a header-only file
skipped += 1
print()
print("=" * 60)
print("Summary:")
print(f" Fixed: {fixed}")
print(f" Skipped: {skipped}")
print(f" Errors: {errors}")
if __name__ == '__main__':
main()