191 lines
6.0 KiB
Python
191 lines
6.0 KiB
Python
"""Quarantine management.
|
|
|
|
When a file cannot be migrated within the retry budget, it is:
|
|
1. Written to ``quarantine/<stem>_<track>_<timestamp>.json`` (machine-readable)
|
|
2. Written to ``quarantine/<stem>_<track>_<timestamp>.md`` (manual-review ticket)
|
|
|
|
The Markdown ticket is the automatically generated "manual ticket" requested
|
|
in the pipeline requirements.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
from migration_pipeline.config import QUARANTINE_DIR
|
|
from migration_pipeline.models import FailureClass, QuarantineEntry
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Qt5 deprecated-API detector
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# A non-exhaustive list of Qt5 APIs that are removed / changed in Qt6.
|
|
_QT5_DEPRECATED_RE = re.compile(
|
|
r"\b("
|
|
r"QRegExp|QTextCodec|QMatrix|QDesktopWidget|"
|
|
r"QSignalMapper|QSortFilterProxyModel::lessThan|"
|
|
r"QAbstractItemModel::reset|"
|
|
r"QApplication::desktop|"
|
|
r"QFontMetrics::width\s*\(|"
|
|
r"QUrl::addQueryItem|"
|
|
r"QSettings::setDefaultFormat|"
|
|
r"QVariant::type\b|"
|
|
r"QMetaType::type\b|"
|
|
r"QTest::qSleep"
|
|
r")\b"
|
|
)
|
|
|
|
|
|
def _extract_affected_apis(code: str) -> list[str]:
|
|
return sorted(set(_QT5_DEPRECATED_RE.findall(code)))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Failure classification
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _classify(
|
|
build_errors: str,
|
|
test_output: str,
|
|
elapsed: float,
|
|
budget: float,
|
|
) -> FailureClass:
|
|
if elapsed >= budget:
|
|
return FailureClass.BUDGET_EXCEEDED
|
|
lower_err = build_errors.lower()
|
|
if "undefined reference" in lower_err or "linker command failed" in lower_err:
|
|
return FailureClass.LINK_ERROR
|
|
if "error:" in build_errors:
|
|
return FailureClass.COMPILE_ERROR
|
|
if "failed" in test_output.lower():
|
|
return FailureClass.TEST_FAILURE
|
|
return FailureClass.UNKNOWN
|
|
|
|
|
|
def _recommend(fc: FailureClass, affected_apis: list[str]) -> str:
|
|
api_str = ", ".join(affected_apis) if affected_apis else "unbekannte APIs"
|
|
if fc == FailureClass.BUDGET_EXCEEDED:
|
|
return (
|
|
"Datei hat das Zeitbudget überschritten. "
|
|
"Empfehlung: Datei manuell in kleinere, unabhängige Units zerlegen und erneut starten."
|
|
)
|
|
if fc == FailureClass.COMPILE_ERROR:
|
|
return (
|
|
f"Compilerfehler. Prüfe Qt6-API-Änderungen für: {api_str}. "
|
|
"Porting Guide: https://doc.qt.io/qt-6/portingguide.html"
|
|
)
|
|
if fc == FailureClass.LINK_ERROR:
|
|
return (
|
|
"Linkfehler. Prüfe Qt6-Modulabhängigkeiten in CMakeLists.txt "
|
|
"(z.B. find_package(Qt6 ...) und target_link_libraries)."
|
|
)
|
|
if fc == FailureClass.TEST_FAILURE:
|
|
return (
|
|
"Semantische Abweichung in Tests. Manuelle Überprüfung der Fachlogik erforderlich. "
|
|
"Vergleiche Qt5- und Qt6-Verhalten für betroffene APIs."
|
|
)
|
|
if fc == FailureClass.CHUNK_MERGE_ERROR:
|
|
return (
|
|
"Chunk-Merge ist inkonsistent. "
|
|
"Empfehlung: Datei als Ganzes manuell migrieren oder Chunk-Grenzen anpassen."
|
|
)
|
|
return "Unbekannter Fehler. Bitte Logs manuell inspizieren."
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Public API
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def quarantine_file(
|
|
*,
|
|
file_path: str,
|
|
track: str,
|
|
build_errors: str,
|
|
test_output: str,
|
|
last_code: str,
|
|
elapsed: float,
|
|
budget: float,
|
|
retry_count: int,
|
|
chunk_info: str,
|
|
) -> QuarantineEntry:
|
|
"""Create a QuarantineEntry and write JSON + Markdown ticket to disk."""
|
|
now = datetime.now(tz=timezone.utc)
|
|
ts = now.strftime("%Y%m%dT%H%M%SZ")
|
|
iso_ts = now.isoformat()
|
|
|
|
fc = _classify(build_errors, test_output, elapsed, budget)
|
|
apis = _extract_affected_apis(last_code)
|
|
recommendation = _recommend(fc, apis)
|
|
|
|
entry = QuarantineEntry(
|
|
file_path=file_path,
|
|
track=track,
|
|
failure_class=fc.value,
|
|
last_error=(build_errors or test_output)[:2000],
|
|
affected_apis=apis,
|
|
chunk_info=chunk_info,
|
|
manual_recommendation=recommendation,
|
|
timestamp=iso_ts,
|
|
retry_count=retry_count,
|
|
elapsed_seconds=round(elapsed, 1),
|
|
)
|
|
|
|
_write_quarantine_files(entry, ts)
|
|
return entry
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# File writers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _write_quarantine_files(entry: QuarantineEntry, ts: str) -> None:
|
|
QUARANTINE_DIR.mkdir(parents=True, exist_ok=True)
|
|
stem = Path(entry.file_path).stem
|
|
base_name = f"{stem}_{entry.track}_{ts}"
|
|
|
|
# --- JSON (machine-readable) -------------------------------------------
|
|
json_path = QUARANTINE_DIR / f"{base_name}.json"
|
|
json_path.write_text(json.dumps(entry.__dict__, indent=2, ensure_ascii=False))
|
|
|
|
# --- Markdown ticket (manual review) ------------------------------------
|
|
md_path = QUARANTINE_DIR / f"{base_name}.md"
|
|
md_path.write_text(_render_ticket(entry))
|
|
|
|
|
|
def _render_ticket(e: QuarantineEntry) -> str:
|
|
apis_md = "\n".join(f"- `{a}`" for a in e.affected_apis) or "_keine erkannt_"
|
|
error_block = e.last_error.strip() or "_kein Fehlertext_"
|
|
return f"""# Quarantäne-Ticket: {Path(e.file_path).name}
|
|
|
|
| Feld | Wert |
|
|
|---|---|
|
|
| **Datei** | `{e.file_path}` |
|
|
| **Track** | `{e.track}` |
|
|
| **Fehlerklasse** | `{e.failure_class}` |
|
|
| **Versuche** | {e.retry_count} |
|
|
| **Laufzeit** | {e.elapsed_seconds:.1f} s |
|
|
| **Zeitstempel** | {e.timestamp} |
|
|
| **Chunk-Info** | {e.chunk_info} |
|
|
|
|
## Letzte Fehlermeldung
|
|
|
|
```
|
|
{error_block}
|
|
```
|
|
|
|
## Betroffene Qt5-APIs
|
|
|
|
{apis_md}
|
|
|
|
## Empfehlung für manuelle Migration
|
|
|
|
{e.manual_recommendation}
|
|
|
|
---
|
|
_Automatisch generiert von der Qt5→Qt6 Migrationspipeline._
|
|
"""
|