- 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
296 lines
11 KiB
Python
296 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Repariert Export-Header-Includes in Qt6 migrierten Dateien.
|
|
|
|
Nur für SHARED/MODULE Libraries - STATIC Libraries brauchen keine Export-Header.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from collections import defaultdict
|
|
|
|
def parse_cmake_file(cmake_path, root_dir):
|
|
"""
|
|
Parse CMakeLists.txt und extrahiere Target-Informationen.
|
|
|
|
Returns:
|
|
targets: dict mapping target_name -> {'type': 'SHARED'|'STATIC'|'MODULE', 'sources': [files]}
|
|
src_to_target: dict mapping source_file (relative path) -> target_name
|
|
"""
|
|
targets = {}
|
|
src_to_target = {}
|
|
|
|
if not os.path.exists(cmake_path):
|
|
return targets, src_to_target
|
|
|
|
with open(cmake_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
content = f.read()
|
|
|
|
# Remove comments
|
|
content = re.sub(r'#[^\n]*', '', content)
|
|
|
|
# Parse SET() variables first
|
|
set_vars = {}
|
|
for match in re.finditer(r'SET\s*\(\s*(\w+)\s+([^)]+)\)', content, re.IGNORECASE):
|
|
var_name = match.group(1)
|
|
var_value = match.group(2).strip()
|
|
# Split on whitespace
|
|
files = [f.strip() for f in var_value.split() if f.strip()]
|
|
set_vars[var_name] = files
|
|
|
|
# Parse NGL_ADD_LIBRARY and NGL_ADD_LIBRARY2
|
|
# Pattern: NGL_ADD_LIBRARY(... target_name SHARED|STATIC|MODULE ...)
|
|
# or: NGL_ADD_LIBRARY2(target_name SHARED|STATIC|MODULE ...)
|
|
|
|
for pattern in [r'NGL_ADD_LIBRARY\s*\(([^)]+)\)', r'NGL_ADD_LIBRARY2\s*\(([^)]+)\)']:
|
|
for match in re.finditer(pattern, content, re.IGNORECASE | re.DOTALL):
|
|
args_str = match.group(1)
|
|
args = args_str.split()
|
|
|
|
if len(args) < 3:
|
|
continue
|
|
|
|
# Skip flags like ALL, WS, AAR, SV, NO_TRANSLATION
|
|
idx = 0
|
|
while idx < len(args) and args[idx].upper() in ['ALL', 'WS', 'AAR', 'SV', 'NO_TRANSLATION']:
|
|
idx += 1
|
|
|
|
if idx >= len(args):
|
|
continue
|
|
|
|
target_name = args[idx].lower() # Target names are lowercase in export headers
|
|
idx += 1
|
|
|
|
if idx >= len(args):
|
|
continue
|
|
|
|
lib_type = args[idx].upper()
|
|
if lib_type not in ['SHARED', 'STATIC', 'MODULE']:
|
|
continue
|
|
|
|
idx += 1
|
|
|
|
# Rest are source files (may include variable references like ${VAR})
|
|
sources = []
|
|
for arg in args[idx:]:
|
|
if arg.startswith('${'):
|
|
# Variable reference - expand it
|
|
var_name = arg[2:-1] # Remove ${ and }
|
|
if var_name in set_vars:
|
|
sources.extend(set_vars[var_name])
|
|
else:
|
|
sources.append(arg)
|
|
|
|
targets[target_name] = {
|
|
'type': lib_type,
|
|
'sources': [s for s in sources if not s.endswith('.h') or 'export' in s.lower()]
|
|
}
|
|
|
|
# Map source files to target (only .cpp/.h files, not export headers)
|
|
cmake_dir = os.path.dirname(cmake_path)
|
|
for src in sources:
|
|
if src.endswith('_export.h'):
|
|
continue # Skip export headers
|
|
if os.path.splitext(src)[1] in ['.cpp', '.h', '.cc', '.cxx', '.hpp']:
|
|
# Store relative path from root_dir
|
|
abs_path = os.path.normpath(os.path.join(cmake_dir, src))
|
|
rel_path = os.path.relpath(abs_path, root_dir)
|
|
src_to_target[rel_path] = target_name
|
|
|
|
return targets, src_to_target
|
|
|
|
|
|
def find_export_header_includes(content):
|
|
"""Find export header includes in file content."""
|
|
# Match both "..." and <...> styles
|
|
pattern = r'#include\s*[<"]([^>"\s]+_export\.h)[>"]'
|
|
return re.findall(pattern, content)
|
|
|
|
|
|
def get_expected_export_header(target_name):
|
|
"""Get expected export header name for a target."""
|
|
return f"{target_name}_export.h"
|
|
|
|
|
|
def fix_file(file_path, target_name, all_targets):
|
|
"""
|
|
Fix export header include in a file.
|
|
|
|
Returns:
|
|
(old_include, new_include, reason) or None if no fix needed
|
|
"""
|
|
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
content = f.read()
|
|
|
|
current_includes = find_export_header_includes(content)
|
|
|
|
if not current_includes:
|
|
return None # No export header include found
|
|
|
|
# Check if target is SHARED or MODULE (STATIC doesn't need export headers)
|
|
if target_name not in all_targets:
|
|
return None # Target not found
|
|
|
|
target_info = all_targets[target_name]
|
|
if target_info['type'] == 'STATIC':
|
|
# STATIC libraries don't need export headers - remove the include
|
|
return (current_includes[0], None, f"STATIC library '{target_name}' doesn't need export header")
|
|
|
|
expected_header = get_expected_export_header(target_name)
|
|
|
|
if len(current_includes) == 1 and current_includes[0] == expected_header:
|
|
return None # Already correct
|
|
|
|
# Fix needed
|
|
old_include = current_includes[0]
|
|
return (old_include, expected_header, f"Target '{target_name}' ({target_info['type']})")
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print("Usage: fix_export_headers_v4.py <base_dir>")
|
|
sys.exit(1)
|
|
|
|
base_dir = sys.argv[1]
|
|
if not os.path.isdir(base_dir):
|
|
print(f"Error: {base_dir} is not a directory")
|
|
sys.exit(1)
|
|
|
|
# Find all CMakeLists.txt files and parse them
|
|
all_targets = {}
|
|
all_src_to_target = {}
|
|
|
|
for root, dirs, files in os.walk(base_dir):
|
|
if 'CMakeLists.txt' in files:
|
|
cmake_path = os.path.join(root, 'CMakeLists.txt')
|
|
targets, src_to_target = parse_cmake_file(cmake_path, base_dir)
|
|
all_targets.update(targets)
|
|
all_src_to_target.update(src_to_target)
|
|
|
|
print(f"Found {len(all_targets)} targets")
|
|
shared_module_targets = [t for t, info in all_targets.items() if info['type'] in ['SHARED', 'MODULE']]
|
|
print(f"SHARED/MODULE targets: {len(shared_module_targets)}")
|
|
|
|
# Find all .h files with export header includes
|
|
files_to_check = []
|
|
for root, dirs, files in os.walk(base_dir):
|
|
for file in files:
|
|
if file.endswith('.h'):
|
|
files_to_check.append(os.path.join(root, file))
|
|
|
|
print(f"Checking {len(files_to_check)} header files...")
|
|
|
|
fixed = []
|
|
skipped = []
|
|
errors = []
|
|
|
|
for file_path in files_to_check:
|
|
rel_path = os.path.relpath(file_path, base_dir)
|
|
|
|
# Check if file has export header includes
|
|
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
content = f.read()
|
|
|
|
current_includes = find_export_header_includes(content)
|
|
if not current_includes:
|
|
skipped.append((file_path, "No export header include"))
|
|
continue
|
|
|
|
# Try to find target for this file
|
|
target_name = None
|
|
|
|
# Priority 1: Exact relative path match
|
|
if rel_path in all_src_to_target:
|
|
target_name = all_src_to_target[rel_path]
|
|
|
|
# Priority 2: Filename match (for headers in same dir as CMakeLists.txt)
|
|
if not target_name:
|
|
filename = os.path.basename(file_path)
|
|
for src_path, tgt in all_src_to_target.items():
|
|
if os.path.basename(src_path) == filename:
|
|
target_name = tgt
|
|
break
|
|
|
|
# Priority 3: Parse local CMakeLists.txt
|
|
if not target_name:
|
|
cmake_path = os.path.join(os.path.dirname(file_path), 'CMakeLists.txt')
|
|
if os.path.exists(cmake_path):
|
|
local_targets, local_mapping = parse_cmake_file(cmake_path, base_dir)
|
|
# Check if file is in any local target
|
|
for tgt_name, tgt_info in local_targets.items():
|
|
for src in tgt_info['sources']:
|
|
if src == os.path.basename(file_path):
|
|
target_name = tgt_name
|
|
break
|
|
|
|
if not target_name:
|
|
errors.append((file_path, "Could not determine target"))
|
|
continue
|
|
|
|
# Fix the file
|
|
result = fix_file(file_path, target_name, all_targets)
|
|
|
|
if result is None:
|
|
skipped.append((file_path, "Already correct or STATIC"))
|
|
continue
|
|
|
|
old_include, new_include, reason = result
|
|
|
|
if new_include is None:
|
|
# Remove include for STATIC libraries
|
|
# Find the line with the include and remove it
|
|
lines = content.split('\n')
|
|
new_lines = []
|
|
for line in lines:
|
|
if f'#include' in line and old_include in line:
|
|
continue # Skip this line
|
|
new_lines.append(line)
|
|
new_content = '\n'.join(new_lines)
|
|
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
f.write(new_content)
|
|
|
|
fixed.append((file_path, old_include, "REMOVED", reason))
|
|
else:
|
|
# Replace include
|
|
# Use regex to handle both "..." and <...>
|
|
pattern = rf'#include\s*[<"]{re.escape(old_include)}[>"]'
|
|
replacement = f'#include "{new_include}"'
|
|
new_content = re.sub(pattern, replacement, content)
|
|
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
f.write(new_content)
|
|
|
|
fixed.append((file_path, old_include, new_include, reason))
|
|
|
|
# Print summary
|
|
print("\n" + "="*80)
|
|
print(f"Summary:")
|
|
print(f" Fixed: {len(fixed)}")
|
|
print(f" Skipped: {len(skipped)}")
|
|
print(f" Errors: {len(errors)}")
|
|
print("="*80)
|
|
|
|
if fixed:
|
|
print("\nFixed files:")
|
|
for item in fixed[:20]: # Show first 20
|
|
if len(item) == 4:
|
|
file_path, old, new, reason = item
|
|
print(f" FIX: {os.path.relpath(file_path, base_dir)}")
|
|
print(f" {old} -> {new}")
|
|
print(f" ({reason})")
|
|
if len(fixed) > 20:
|
|
print(f" ... and {len(fixed) - 20} more")
|
|
|
|
if errors:
|
|
print("\nErrors:")
|
|
for file_path, reason in errors[:20]:
|
|
print(f" ERROR: {os.path.relpath(file_path, base_dir)} - {reason}")
|
|
if len(errors) > 20:
|
|
print(f" ... and {len(errors) - 20} more")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main() |