19 KiB
Qt5 → Qt6 Migrationspipeline
Asynchrone, agentische Pipeline zur automatisierten Migration einer C++/Qt5-Codebasis nach Qt6.
Entwickelt für den Pilotbatch Q1 (~32 k LOC, 115 Files) als Vorstufe zur vollständigen Q2-Migration (~1,2 Mio LOC, ~6 500 Files).
Inhaltsverzeichnis
- Überblick
- Architektur
- Dual-Track-Strategie
- Semantisches Chunking
- Multi-Turn-Konversation
- Quality Gates
- Quarantäne & Tickets
- Voraussetzungen
- Einrichtung
- Docker
- Ausführung
- Ausgaben
- Konfigurationsreferenz
- Modulübersicht
- Burn-Phase & Skalierung auf Q2
- FAQ
Überblick
Die Pipeline übernimmt folgende Aufgaben vollautomatisch:
| Schritt | Beschreibung |
|---|---|
| Batch-Isolation | Pro Batch + Track ein eigener Git-Worktree und Build-Verzeichnis |
| LLM-Migration | Qt5-APIs werden per LLM (Scaleway) auf Qt6 portiert |
| Chunk-Verarbeitung | Dateien > 500 Zeilen werden semantisch gesplittet und parallel migriert |
| Syntax-Check | Schneller Per-File-Check via compile_commands.json ohne Full-Rebuild |
| Test-Generierung | QTest (Qt-naher Code) oder gtest + Smoke-Test pro Datei |
| Quality Gates | Build ✓ · Bestehende Tests ✓ · Neue Tests ✓ |
| Quarantäne | Dateien, die Gates nicht bestehen, werden isoliert + Ticket erzeugt |
| Vergleichsreport | Track A vs. Track B als Markdown + JSON |
Architektur
run_migration.py ← CLI-Einstiegspunkt
│
└── migration_pipeline/
├── orchestrator.py ← Batch-Steuerung, beide Tracks parallel
│ ├── git_manager.py ← Worktree + Branch pro Batch×Track
│ ├── build_runner.py ← cmake configure/build/ctest (isoliert)
│ └── migrator.py ← Per-File Agentenloop
│ ├── chunker.py ← Semantisches C++ Chunking
│ ├── api_client.py ← Scaleway API
│ ├── test_generator.py ← QTest / gtest / Smoke
│ ├── quality_gate.py ← Gate-Auswertung
│ └── quarantine.py ← Quarantäne + Ticket
├── reporter.py ← Vergleichsreport A vs B
├── models.py ← Dataclasses & Enums
└── config.py ← Alle Konstanten
Datenfluss pro Datei
Quelldatei
│
▼ split_semantic() wenn > 500 Zeilen
[Chunk 1] [Chunk 2] … [Chunk N] ← parallel per LLM migriert
│
▼ Consistency-Pass (Merge-Check via LLM)
Migrierter Code
│
▼ syntax_check_file() (compile_commands.json, kein Full-Rebuild)
│
▼ generate_tests() (QTest oder gtest + Smoke)
│
▼ cmake_configure() + cmake_build() + ctest
│
┌──┴──────────────────┐
│ Quality Gates │
│ ✓ Build │
│ ✓ Bestehende Tests │
│ ✓ Neue Tests │
└──┬──────────────────-┘
│ └── FAIL → Retry (max 3×)
│ └── Quarantäne
▼
git commit (Branch migration/<batch>/<track>)
Dual-Track-Strategie
Beide Tracks laufen gleichzeitig auf dem selben Batch.
| Track A – Konservativ | Track B – Modern | |
|---|---|---|
| Ziel | Qt5→Qt6 Kompatibilität, minimale Änderungen | Qt6 + C++20 Modernisierung |
| Stil | Original-Stil beibehalten | Ranges, Concepts, std::optional, Lambdas, [[nodiscard]] usw. |
| Git-Branch | migration/<batch>/track_a_conservative |
migration/<batch>/track_b_modern |
| Risiko | Niedrig | Mittel |
Nach Abschluss des Pilotbatches folgt die Burn-Phase: beide Tracks werden identischen Gates unterzogen und anhand von Erfolgsquote, Warnungsanzahl und Wartbarkeit verglichen. Der Vergleichsreport enthält eine automatische Empfehlung.
Semantisches Chunking
Große Dateien (> 500 Zeilen) werden nicht an willkürlichen Zeilengrenzen getrennt, sondern an:
- Klassen-/Struct-/Namespace-Definitionen (
class Foo {,namespace Bar {) - Funktionsdefinitionen (Top-Level und Methoden)
- Fallback: nächste Leerzeile unterhalb der 500-Zeilen-Marke
Alle Chunks einer Datei werden parallel per API migriert. Anschließend läuft ein Consistency-Pass (separater LLM-Aufruf), der:
- doppelte
#pragma once/ Include-Guards entfernt, - offene Namespaces schließt,
- doppelte Deklarationen bereinigt.
Multi-Turn-Konversation
Die Retry-Schleife des Migrations-Agenten verwendet eine wachsende Gesprächshistorie, sodass das Modell beim nächsten Versuch seine eigene fehlerhafte Ausgabe zusammen mit der Compiler-Fehlermeldung sieht – analog zum Verhalten des Copilot CLI.
Konversationsstruktur
Iteration 0: [system, user(„Portiere folgenden Qt5-Code…")]
→ reply_v1
Iteration 1: [system, user(„Portiere…"), assistant(reply_v1),
user(„Fehler im vorherigen Versuch:\n<fehler>\nBitte korrigiere:\n<code>")]
→ reply_v2
Drei unabhängige Konversations-Scopes
| Scope | Lebensdauer | Zweck |
|---|---|---|
Whole-file retry (whole_messages) |
Pro Datei; wächst über alle Retries | Modell lernt iterativ aus Compiler-Fehlern seiner eigenen Ausgabe |
| Chunk-Migration (je Chunk) | Pro Chunk, frisch + unabhängig | Parallele Chunk-Verarbeitung ohne gegenseitige Beeinflussung |
| Consistency-Pass (frisch) | Pro Datei, einmalig nach Chunk-Merge | Isoliert vom Retry-Loop; prüft nur Merge-Konsistenz |
Per-File-Scoping
whole_messages ist eine lokale Variable innerhalb von migrate_file und wird nach Rückgabe der Funktion verworfen. Gesprächshistorie wird niemals zwischen Dateien oder Batches geteilt.
Layered API in api_client.py
| Funktion | Typ | Verwendung |
|---|---|---|
_send_messages(client, model, messages) |
privat | Einziger HTTP-Aufruf; nimmt eine fertige Message-Liste entgegen |
call_api(client, model, system, user) |
öffentlich, stateless | Baut [system, user] und delegiert – rückwärtskompatibel |
call_api_multiturn(client, model, messages) |
öffentlich, multi-turn | Übergibt die komplette Verlaufsliste direkt an _send_messages |
test_generator.py verwendet weiterhin das stateless call_api; nur migrator.py nutzt call_api_multiturn.
Quality Gates
| Gate | Bedingung | Blockierend? |
|---|---|---|
| Build | cmake --build returncode 0 |
✅ Ja |
| Bestehende Tests | Vollständiger CTest-Lauf grün | ✅ Ja |
| Neue Tests | Generierte Unit- + Smoke-Tests grün | ✅ Ja |
| Warnungen | Warnungsanzahl = 0 | ⚠️ Advisory (nicht blockierend) |
Warnungen werden vollständig geloggt und im Vergleichsreport ausgewiesen. Sie blockieren die Pipeline nicht (konfigurierbar über WARNINGS_ARE_BLOCKING=True in config.py).
Fast Syntax-Check
Vor dem teuren Full-Build wird jede Datei einzeln per compile_commands.json (erzeugt bei cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON) syntaxgeprüft:
clang++ -fsyntax-only <compile_flags_aus_json> <datei.cpp>
Das spart bei einer 1,2-Mio-LOC-Codebasis erheblich Zeit.
Quarantäne & Tickets
Dateien werden in Quarantäne verschoben, wenn:
- 3 Retry-Versuche erschöpft sind, oder
- das Zeitbudget überschritten wurde (10 min für kleine Dateien, 25 min für Dateien > 1 000 Zeilen).
Quarantäne ist nicht-blockierend: der Rest des Batches läuft weiter.
Generierte Artefakte
Für jede quarantänisierte Datei werden zwei Dateien in quarantine/ erzeugt:
<stem>_<track>_<timestamp>.json – maschinenlesbar:
{
"file_path": "src/core/MyClass.cpp",
"track": "track_a_conservative",
"failure_class": "compile_error",
"last_error": "error: 'QRegExp' was not declared ...",
"affected_apis": ["QRegExp"],
"chunk_info": "3 chunks, ~500 Zeilen/Chunk",
"manual_recommendation": "Compile-Fehler. Prüfe Qt6-API-Änderungen für: QRegExp.",
"timestamp": "2026-07-08T18:32:12Z",
"retry_count": 3,
"elapsed_seconds": 187.4
}
<stem>_<track>_<timestamp>.md – manuelles Review-Ticket:
# Quarantäne-Ticket: MyClass.cpp
| Feld | Wert |
|---|---|
| Fehlerklasse | compile_error |
| Empfehlung | Qt6 Porting Guide: QRegExp … |
…
Fehlerklassen
| Klasse | Bedeutung |
|---|---|
compile_error |
Compilerfehler (Qt5-API entfernt / geändert) |
link_error |
Linkfehler (fehlende Qt6-Modulabhängigkeit) |
test_failure |
Semantische Abweichung in Tests |
test_build_failure |
Generierter Test kompiliert nicht |
budget_exceeded |
Zeitbudget überschritten |
api_error |
Scaleway-API nicht erreichbar |
chunk_merge_error |
Inkonsistenz nach Chunk-Merge |
Voraussetzungen
System
- Python ≥ 3.11
- cmake ≥ 3.22
- clang++ (für
syntax_check_file-Fallback; optional wenncompile_commands.jsonvorhanden) - git ≥ 2.35 (für
git worktree) - GTest und Qt6 installiert und für cmake auffindbar
Scaleway API
- Zugang zur Scaleway AI API
- Modelle:
glm-5.2(große Dateien) ·qwen3.6-35b-a3b(kleine Dateien)
Einrichtung
# 1. Abhängigkeiten installieren
pip install -r requirements.txt
# 2. API-Key setzen
echo "SCW_SECRET_KEY=your_key_here" > .env
# 3. Prioritätsliste erstellen (eine Datei pro Zeile, in Bearbeitungsreihenfolge)
cat > q1_files.txt << 'EOF'
# cmake-Dateien zuerst (beeinflussen Build aller anderen)
src/CMakeLists.txt
src/core/CMakeLists.txt
# Header-Dateien
src/core/MyClass.h
src/core/OtherClass.h
# Implementierungsdateien
src/core/MyClass.cpp
src/core/OtherClass.cpp
EOF
Dateireihenfolge: Die Pipeline verarbeitet Dateien exakt in der angegebenen Reihenfolge (innerhalb des Tracks). Empfohlene Reihenfolge: cmake-Dateien → Header → kleine .cpp → große .cpp.
Docker
Das mitgelieferte Dockerfile enthält alle System-Abhängigkeiten (Ubuntu 22.04, Python 3.11, cmake, clang, git) und eignet sich für eine reproduzierbare Ausführung ohne lokale Installation.
Image bauen & starten (empfohlen: run_docker.sh)
# Build + interaktive Shell (Host-UID/GID werden automatisch gemappt)
./run_docker.sh
# Build + direkte Ausführung der Pipeline
./run_docker.sh python3.11 run_migration.py \
--batch-id q1_pilot \
--file-list ./q1_files.txt
Das Skript:
- baut das Image unter dem Namen
migration-pipeline:ubuntu22.04, - mountet das aktuelle Verzeichnis als
/app(Änderungen wirken sofort), - mappt die Host-UID/GID, sodass erstellte Dateien dem aufrufenden Benutzer gehören,
- vergibt bei Bedarf
sudo-Rechte im Container.
Manuelles docker build / docker run
# Image bauen
docker build -t migration-pipeline:ubuntu22.04 .
# API-Key übergeben und Pipeline starten
docker run --rm -it \
-v "$(pwd):/app" \
-w /app \
-e SCW_SECRET_KEY="your_key_here" \
migration-pipeline:ubuntu22.04 \
python3.11 run_migration.py \
--batch-id q1_pilot \
--file-list ./q1_files.txt
# Dry-Run (kein API-Aufruf, kein Key erforderlich)
docker run --rm -it \
-v "$(pwd):/app" \
-w /app \
migration-pipeline:ubuntu22.04 \
python3.11 run_migration.py \
--batch-id q1_pilot \
--file-list ./q1_files.txt \
--dry-run
Hinweise
| Thema | Empfehlung |
|---|---|
| Scaleway-Env | Benötigt: SCW_ACCESS_KEY, SCW_SECRET_KEY, SCW_DEFAULT_ORGANIZATION_ID, SCW_DEFAULT_PROJECT_ID, SCW_SERVERLESS (runtime env oder .env) |
| Qt6 / GTest | Nicht im Image enthalten – das C++-Repository muss bereits konfigurierte cmake-Targets mitbringen oder per Bind-Mount bereitgestellt werden |
| Image-Name | Über die Umgebungsvariable IMAGE_NAME anpassbar, z.B. IMAGE_NAME=my-registry/migration-pipeline:1.0 ./run_docker.sh |
Ausführung
Dry-Run (kein API-Aufruf)
python run_migration.py \
--batch-id q1_pilot \
--file-list ./q1_files.txt \
--dry-run
Gibt Dateiliste mit Zeilenanzahl und Chunk-Schätzung aus.
Pilotbatch Q1
python run_migration.py \
--batch-id q1_pilot \
--file-list ./q1_files.txt \
--model glm \
--repo-root . \
--source-root ./src
Mit Debug-Ausgabe
python run_migration.py --batch-id q1_pilot --file-list ./q1_files.txt -v
Alle Optionen
--batch-id Eindeutiger Name für diesen Batch (z.B. q1_pilot)
--file-list Pfad zur priorisierten Dateiliste
--repo-root Wurzel des git-Repositories (Standard: .)
--source-root Wurzel des C++ Quellbaums (Standard: .)
--model Modellfamilie für API-Aufrufe: glm|qwen (Standard: glm)
--dry-run Nur Dateiliste und Chunk-Schätzung ausgeben
--verbose, -v Debug-Logging aktivieren
Ausgaben
workspace/
q1_pilot/
track_a_conservative/ ← git worktree, Branch migration/q1_pilot/track_a_conservative
src/... ← migrierte Quelldateien (nach jedem Erfolg committed)
tests_generated/ ← generierte Unit- und Smoke-Tests
build/ ← isoliertes cmake Build-Verzeichnis
track_b_modern/ ← analog für Track B
quarantine/
MyClass_track_a_conservative_20260708T183200Z.json
MyClass_track_a_conservative_20260708T183200Z.md ← Review-Ticket
reports/
q1_pilot_comparison.md ← Track A vs B Vergleich (Markdown)
q1_pilot_comparison.json ← Track A vs B Vergleich (maschinenlesbar)
Beispiel-Konsolenausgabe
================================================================
Batch: q1_pilot
================================================================
Track A (konservativ): 108/115 OK | 7 quarantined | 23 warnings | avg 142s
Track B (modern) : 97/115 OK | 18 quarantined | 41 warnings | avg 198s
================================================================
Konfigurationsreferenz
Alle Werte in migration_pipeline/config.py:
| Variable | Standard | Beschreibung |
|---|---|---|
MODEL_GLM |
glm-5.2 |
Modell-ID der GLM-Familie |
MODEL_QWEN |
qwen3.5-397b-a17b |
Modell-ID der QWEN-Familie |
DEFAULT_MODEL_FAMILY |
glm |
Standard-Auswahl für --model |
LARGE_FILE_THRESHOLD |
1000 |
Zeilengrenze für Time-Budget (klein/groß) |
MAX_CONCURRENT_TASKS |
3 |
Parallele Worker pro Track |
CHUNK_SIZE_LINES |
500 |
Max. Zeilen pro Chunk |
SOFT_BUDGET_PER_FILE |
600 s |
Soft-Budget (Warnung, kein Abbruch) |
HARD_BUDGET_SMALL_FILE |
600 s |
Hard-Budget für kleine Dateien |
HARD_BUDGET_LARGE_FILE |
1500 s |
Hard-Budget für große Dateien |
MAX_RETRIES |
3 |
Max. Versuche pro Datei und Track |
WARNINGS_ARE_BLOCKING |
False |
True = Warnungen blockieren Gate |
WORKSPACE_ROOT |
workspace/ |
Verzeichnis für git-Worktrees |
QUARANTINE_DIR |
quarantine/ |
Quarantäne-Artefakte |
REPORTS_DIR |
reports/ |
Vergleichsreports |
Modulübersicht
| Modul | Verantwortung |
|---|---|
config.py |
Zentralkonfiguration, Konstanten, Umgebungsvariablen |
models.py |
Dataclasses (FileResult, BatchResult, QuarantineEntry usw.), Enums |
chunker.py |
Semantisches C++ Chunking an Klassen-/Funktionsgrenzen |
api_client.py |
Scaleway API Client (async, httpx); layered API: stateless call_api + multi-turn call_api_multiturn |
build_runner.py |
cmake configure/build, CTest, compile_commands.json-Parser |
test_generator.py |
QTest/gtest/Smoke-Generierung, idempotente CMakeLists-Registrierung |
quality_gate.py |
Aggregierte Gate-Auswertung (Build · bestehende Tests · neue Tests) |
quarantine.py |
Fehlerklassifikation, Qt5-API-Erkennung, JSON+Markdown-Ticket |
git_manager.py |
git worktree Lifecycle, Per-File-Commits |
migrator.py |
Per-File Agentenloop: Chunk-Strategie, Multi-Turn-Retry-Konversation, Budget-Kontrolle |
orchestrator.py |
Batch-Orchestrierung, Dual-Track parallel, cmake-Konfiguration |
reporter.py |
Track A vs B Vergleichsreport (Markdown + JSON) |
run_migration.py |
CLI, Argument-Parsing, Dry-Run |
Burn-Phase & Skalierung auf Q2
Burn-Phase (nach Q1)
- Beide Branches (
track_a_conservative,track_b_modern) auf identischer Testinfrastruktur deployen. - Stabilität, Warnungsfreiheit und Wartbarkeit vergleichen (Report
q1_pilot_comparison.md). - Gewinner-Track als Basis für Q2 wählen (oder hybride Strategie: Track A für risikoarme Module, Track B für gut getestete Module).
Skalierung auf Q2
| Parameter | Q1 (Pilot) | Q2 (Vollmigration) |
|---|---|---|
| Dateien | ~115 | ~6 500 |
| LOC | ~32 000 | ~1 200 000 |
| Empfohlene Worker | 3 | 5–8 (anpassen je API-Rate-Limit) |
| Batches | 1 | ~57 (à 115 Files) |
| Erwartete Laufzeit* | ~4–6 h | ~200–300 h gesamt |
*Schätzung bei 3 Workern, avg. 150 s/Datei, 80 % Erfolgsquote.
Empfohlene Batch-Größe für Q2: 100–150 Files (ca. 30 k LOC), sortiert nach Abhängigkeitstiefe (cmake → Header → Implementierung).
FAQ
Warum git worktrees statt direktem Arbeiten im Repository?
Damit Track A und Track B niemals denselben Dateisystem- oder Build-Zustand teilen. Async-Worker würden sich sonst gegenseitig überschreiben.
Was passiert mit quarantänierten Dateien nach dem Batch?
Sie werden wiederhergestellt (Original bleibt erhalten), das Ticket landet in quarantine/. Die Datei ist mit Fehlerklasse, betroffenen APIs und Empfehlung indiziert und kann gezielt manuell migriert werden.
Kann ich nur Track A oder nur Track B laufen lassen?
Aktuell nicht über CLI-Flag. Einfachste Lösung: TRACK_B_ID in orchestrator.py auskommentieren. Ein --track CLI-Parameter ist als spätere Erweiterung vorgesehen.
Was tun bei Rate-Limiting der Scaleway API?
MAX_CONCURRENT_TASKS in config.py reduzieren. Alternativ Exponential-Backoff in api_client.py ergänzen.
Sind die generierten Tests vollständig korrekt?
Nein – sie sind ein Ausgangspunkt. Die Quality Gates stellen sicher, dass sie kompilieren und laufen, aber keine logischen Fehler enthalten. Manuelle Nachkontrolle empfohlen, besonders für fachkritische Module.
Wie wird mit .cmake-Dateien umgegangen?
.cmake-Dateien werden wie C++ Dateien per LLM migriert (cmake-spezifische Qt5→Qt6 Syntax). Da sie nicht kompiliert werden, entfällt der Syntax-Check; die Gates Build/Test gelten nach dem nächsten Full-Build.