diff --git a/Dockerfile.ci b/Dockerfile.ci new file mode 100644 index 0000000..c03b068 --- /dev/null +++ b/Dockerfile.ci @@ -0,0 +1,23 @@ +FROM debian:bookworm-slim + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + python3 \ + clang-format \ + cmake \ + ninja-build \ + git \ + g++ \ + make \ + ca-certificates \ + pkg-config \ + qtbase5-dev \ + qt6-base-dev \ + qt6-base-dev-tools \ + qt6-tools-dev \ + qt6-tools-dev-tools && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /workspace diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..4516dad --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,84 @@ +pipeline { + agent { + docker { + image 'lifemaps/ci-cpp:latest' + reuseNode true + args '-u root:root' + } + } + + options { + timestamps() + disableConcurrentBuilds() + } + + environment { + PROJECT_DIR = "qt6-after/qt6-migrated-benchmark" + BUILD_DIR = "build" + CHANGE_TARGET = "${env.CHANGE_TARGET ?: 'origin/main'}" + } + + stages { + stage('Checkout') { + steps { + checkout scm + sh 'git fetch --all --prune' + sh 'git submodule update --init --recursive || true' + } + } + + stage('Tool Versions') { + steps { + sh ''' + python3 --version + cmake --version + ninja --version + g++ --version + ''' + } + } + + stage('Qt Diagnostics') { + steps { + sh ''' + dpkg -l | grep -E "qt6-base-dev|qtbase5-dev|qt6-tools" || true + pkg-config --list-all | grep -i qt || true + ''' + } + } + + stage('Policy Checks') { + steps { + sh 'python3 ci/check_pr_policies.py' + } + } + + stage('Configure') { + steps { + sh 'cmake -S ${PROJECT_DIR} -B ${BUILD_DIR} -G Ninja -DCMAKE_BUILD_TYPE=Debug' + } + } + + stage('Build') { + steps { + sh 'cmake --build ${BUILD_DIR} --parallel' + } + } + + stage('Test') { + steps { + sh 'ctest --test-dir ${BUILD_DIR} --output-on-failure' + } + } + } + + post { + always { + script { + if (getContext(hudson.FilePath)) { + cleanWs deleteDirs: true, disableDeferredWipeout: true + } + } + } + } +} diff --git a/README.md b/README.md index 927179f..073b654 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,69 @@ -# qt-migration-copilot-demo +# Qt Migration Copilot Demo -Qt Migration Copilot Demo \ No newline at end of file +Dieses Repository zeigt einen kleinen, reproduzierbaren Benchmark für die Migration eines alten Qt5/C++11-Projekts nach Qt6/C++20 mit Copilot CLI. + +## Inhalt + +- `qt5-before/` + Ausgangszustand mit absichtlich veralteten Qt5-APIs: + - `QRegExp` + - `QDesktopWidget` + - `QTextCodec` + - alte `SIGNAL/SLOT`-Syntax + - getrennte `Qt5::Core`, `Qt5::Gui`, `Qt5::Widgets` + +- `qt6-after/` + Erwarteter oder referenzierter Zielzustand nach der Migration: + - `QRegularExpression` + - `QScreen` + - modernes UTF-8-/String-Handling + - moderne Connect-Syntax + - Qt6 + C++20 + +- `Dockerfile.ci` + Vorgebautes CI-Image für Jenkins. + +- `Jenkinsfile` + Führt Configure, Build und Test im vorgebauten Docker-Image aus. + +- `scripts/` + Hilfsskripte für Entire CLI und Copilot CLI. + +## Ziel + +Das Repo dient als: +- Demo für Qt5→Qt6-Migration +- Benchmark für Copilot CLI / Agenten +- reproduzierbares Beispiel mit Entire-Checkpoints +- Referenz für Jenkins + Docker CI + +## Empfohlener Workflow + +1. In `qt5-before/` starten +2. Entire für Copilot CLI aktivieren +3. Baseline-Checkpoint committen +4. Copilot-Migration ausführen +5. Ergebnis committen +6. Mit `qt6-after/` vergleichen + +## CI-Image bauen + +```bash +docker build -t lifemaps/ci-cpp:latest -f Dockerfile.ci . +docker push lifemaps/ci-cpp:latest +``` + +## Entire + Copilot + +Beispiel: + +```bash +./scripts/run-entire-copilot-migration-with-optional-rewind-resume.sh qt5-before/qt5-legacy-migration-benchmark +``` + +## Hinweis + +Dieses Repository ist absichtlich klein gehalten, damit: +- Migrationsänderungen gut sichtbar bleiben +- Copilot-Antworten vergleichbar sind +- CI-Läufe schnell reproduzierbar sind diff --git a/ci/check_pr_policies.py b/ci/check_pr_policies.py new file mode 100755 index 0000000..6df2f89 --- /dev/null +++ b/ci/check_pr_policies.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +import os +import subprocess +import sys +from pathlib import Path + +ROOT = Path(".").resolve() +TEST_DIRS = [ + ROOT / "tests", + ROOT / "test", + ROOT / "qt6-after" / "qt6-migrated-benchmark" / "tests", +] +CPP_EXTENSIONS = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"} + +TEST_PATTERNS = [ + "test_{name}.cpp", + "test_{name}.h", + "{name}_test.cpp", + "{name}_test.h", + "{name}test.cpp", + "{name}test.h", + "{name}gtest.cpp", + "{name}gtest.h", +] + +def run(cmd: str) -> str: + return subprocess.check_output(cmd, shell=True, text=True).strip() + +def git_ref_exists(ref: str) -> bool: + try: + subprocess.check_call( + f"git rev-parse --verify --quiet '{ref}'", + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return True + except subprocess.CalledProcessError: + return False + +def is_pull_request_build() -> bool: + return bool(os.environ.get("CHANGE_ID")) + +def resolve_target_ref() -> str | None: + raw = os.environ.get("CHANGE_TARGET", "").strip() + + candidates = [] + if raw: + candidates.append(raw) + if not raw.startswith("origin/"): + candidates.append(f"origin/{raw}") + + candidates.extend(["origin/main", "main"]) + + seen = set() + for ref in candidates: + if ref and ref not in seen: + seen.add(ref) + if git_ref_exists(ref): + return ref + + return None + +def changed_files(): + target = resolve_target_ref() + + if target: + print(f"Vergleiche gegen Ziel-Ref: {target}") + out = run(f"git diff --name-only {target}...HEAD") + return [line.strip() for line in out.splitlines() if line.strip()] + + if is_pull_request_build(): + print("ERROR: Kein gültiger Ziel-Branch-Ref für PR-Build gefunden.") + sys.exit(1) + + if git_ref_exists("HEAD~1"): + print("Kein Ziel-Branch-Ref gefunden, verwende HEAD~1 für Branch-Build.") + out = run("git diff --name-only HEAD~1..HEAD") + return [line.strip() for line in out.splitlines() if line.strip()] + + print("Keine Vergleichsbasis gefunden, behandle alle versionierten Dateien als geändert.") + out = run("git ls-files") + return [line.strip() for line in out.splitlines() if line.strip()] + +def is_cpp_file(path: Path) -> bool: + return path.suffix.lower() in CPP_EXTENSIONS + +def require_clang_format(): + if not (ROOT / ".clang-format").exists(): + print("ERROR: .clang-format fehlt im Repository-Root.") + sys.exit(1) + +def check_format(path: Path): + subprocess.check_call(f"clang-format --dry-run --Werror '{path}'", shell=True) + +def candidate_names(path: Path): + stem = path.stem.lower() + names = {stem} + for suffix in ["_p", "_impl", "_private", "_priv", "_ui"]: + if stem.endswith(suffix): + names.add(stem[:-len(suffix)]) + return sorted(n for n in names if n) + +def iter_test_files(): + for test_dir in TEST_DIRS: + if test_dir.exists(): + yield from (p for p in test_dir.rglob("*") if p.is_file()) + +def has_related_test(path: Path) -> bool: + test_files = list(iter_test_files()) + if not test_files: + return False + + test_names = [p.name.lower() for p in test_files] + + for name in candidate_names(path): + for pattern in TEST_PATTERNS: + if pattern.format(name=name).lower() in test_names: + return True + for test_name in test_names: + if name in test_name and ("test" in test_name or "gtest" in test_name): + return True + return False + +def main(): + require_clang_format() + + files = changed_files() + if not files: + print("Keine geänderten Dateien erkannt.") + return + + for rel in files: + path = ROOT / rel + if not path.exists(): + continue + if is_cpp_file(path): + print(f"Prüfe: {rel}") + try: + check_format(path) + except subprocess.CalledProcessError: + print(f"ERROR: Formatfehler in {rel}") + sys.exit(1) + + if not has_related_test(path): + print(f"ERROR: Kein passender Test unter tests/, test/ oder qt6-after/qt6-migrated-benchmark/tests für {rel} gefunden.") + sys.exit(1) + + print("Policy-Checks erfolgreich.") + +if __name__ == "__main__": + main() diff --git a/qt5-before/qt5-legacy-migration-benchmark/.gitkeep b/qt5-before/qt5-legacy-migration-benchmark/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/qt5-before/qt5-legacy-migration-benchmark/CMakeLists.txt b/qt5-before/qt5-legacy-migration-benchmark/CMakeLists.txt new file mode 100644 index 0000000..4c9f1ef --- /dev/null +++ b/qt5-before/qt5-legacy-migration-benchmark/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.10) + +project(Qt5LegacyMigrationBenchmark LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets) + +add_executable(Qt5LegacyMigrationBenchmark + main.cpp + MainWindow.h + MainWindow.cpp + MainWindow.ui + resources.qrc +) + +target_link_libraries(Qt5LegacyMigrationBenchmark + Qt5::Core + Qt5::Gui + Qt5::Widgets +) diff --git a/qt5-before/qt5-legacy-migration-benchmark/MainWindow.cpp b/qt5-before/qt5-legacy-migration-benchmark/MainWindow.cpp new file mode 100644 index 0000000..b803984 --- /dev/null +++ b/qt5-before/qt5-legacy-migration-benchmark/MainWindow.cpp @@ -0,0 +1,53 @@ +#include "MainWindow.h" +#include "ui_MainWindow.h" + +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , legacyPattern("^[A-Za-z]+\\d+$") +{ + ui->setupUi(this); + + QTextCodec *codec = QTextCodec::codecForName("UTF-8"); + QString title = codec->toUnicode("Qt5 Legacy Migration Benchmark"); + setWindowTitle(title); + + ui->label->setText(codec->toUnicode("Bitte Text wie Test123 eingeben.")); + ui->iconLabel->setPixmap(QPixmap(":/icons/app.png").scaled(48, 48)); + + connect(ui->validateButton, SIGNAL(clicked()), this, SLOT(onValidateClicked())); + connect(ui->screenButton, SIGNAL(clicked()), this, SLOT(onScreenInfoClicked())); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::onValidateClicked() +{ + QString text = ui->lineEdit->text(); + + if (legacyPattern.exactMatch(text)) { + ui->resultLabel->setText("Match"); + } else { + ui->resultLabel->setText("No match"); + } +} + +void MainWindow::onScreenInfoClicked() +{ + QRect screenGeometry = QApplication::desktop()->screenGeometry(); + QString info = QString("Screen: %1x%2") + .arg(screenGeometry.width()) + .arg(screenGeometry.height()); + + ui->resultLabel->setText(info); +} diff --git a/qt5-before/qt5-legacy-migration-benchmark/MainWindow.h b/qt5-before/qt5-legacy-migration-benchmark/MainWindow.h new file mode 100644 index 0000000..a3b12f2 --- /dev/null +++ b/qt5-before/qt5-legacy-migration-benchmark/MainWindow.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private slots: + void onValidateClicked(); + void onScreenInfoClicked(); + +private: + Ui::MainWindow *ui; + QRegExp legacyPattern; +}; diff --git a/qt5-before/qt5-legacy-migration-benchmark/MainWindow.ui b/qt5-before/qt5-legacy-migration-benchmark/MainWindow.ui new file mode 100644 index 0000000..75c99ee --- /dev/null +++ b/qt5-before/qt5-legacy-migration-benchmark/MainWindow.ui @@ -0,0 +1,69 @@ + + + MainWindow + + + + 0 + 0 + 420 + 240 + + + + Qt5 Legacy App + + + + + + + Initial text + + + + + + + Enter value like Test123 + + + + + + + + + + + + + + + + Validate + + + + + + + Screen Info + + + + + + + + + Result appears here + + + + + + + + + diff --git a/qt5-before/qt5-legacy-migration-benchmark/app.png b/qt5-before/qt5-legacy-migration-benchmark/app.png new file mode 100644 index 0000000..64b0fad Binary files /dev/null and b/qt5-before/qt5-legacy-migration-benchmark/app.png differ diff --git a/qt5-before/qt5-legacy-migration-benchmark/main.cpp b/qt5-before/qt5-legacy-migration-benchmark/main.cpp new file mode 100644 index 0000000..6654b27 --- /dev/null +++ b/qt5-before/qt5-legacy-migration-benchmark/main.cpp @@ -0,0 +1,16 @@ +#include +#include +#include "MainWindow.h" + +int main(int argc, char *argv[]) +{ +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); +#endif + + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/qt5-before/qt5-legacy-migration-benchmark/resources.qrc b/qt5-before/qt5-legacy-migration-benchmark/resources.qrc new file mode 100644 index 0000000..05f5322 --- /dev/null +++ b/qt5-before/qt5-legacy-migration-benchmark/resources.qrc @@ -0,0 +1,5 @@ + + + app.png + + diff --git a/qt6-after/qt6-migrated-benchmark/.gitkeep b/qt6-after/qt6-migrated-benchmark/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/qt6-after/qt6-migrated-benchmark/tests/.gitkeep b/qt6-after/qt6-migrated-benchmark/tests/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..32f8478 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,216 @@ +# scripts/ + +Dieses Verzeichnis enthält Hilfsskripte für den Demo- und Benchmark-Workflow rund um: + +- **Qt5 → Qt6** +- **C++11 → C++23** +- **Copilot CLI** +- **Entire CLI** +- **Scaleway BYOK / OpenAI-kompatibler Provider** +- **lokale Reproduzierbarkeit der Migration** + +## Übersicht + +### `create-qt5-legacy-benchmark.sh` +Erzeugt das Legacy-Beispielprojekt unter: + +```text +qt5-before/qt5-legacy-migration-benchmark +``` + +Das Projekt ist absichtlich veraltet aufgebaut, um eine realistische Migration zu erzwingen, z. B. mit: + +- `QRegExp` +- `QDesktopWidget` +- `QTextCodec` +- alter `SIGNAL/SLOT`-Syntax +- `Qt5::Core`, `Qt5::Gui`, `Qt5::Widgets` +- `C++11` + +### `run-entire-copilot-migration.sh` +Standard-Workflow für eine Copilot-CLI-Migration mit Entire: + +1. Entire für Copilot CLI aktivieren +2. Baseline committen +3. Copilot-Session starten +4. Ergebnis committen +5. Checkpoints anzeigen +6. letzten Stand mit Entire erklären + +Dieses Skript ist für den normalen Migrationslauf gedacht. + +### `run-entire-copilot-migration-with-optional-rewind-resume.sh` +Wie das Standardskript, aber zusätzlich mit optionalem: + +- `entire checkpoint rewind` +- `entire session resume ` + +**Wichtig:** +Die Entire-Dokumentation ist bei Copilot CLI in Bezug auf Rewind/Resume nicht ganz konsistent. +Die Copilot-CLI-Seite beschreibt Rewind und Resume, während die allgemeine Agent-Übersicht teils eingeschränkte Aussagen macht. Entire installiert Copilot-CLI-Hooks mit `entire agent add copilot-cli`, erstellt Checkpoints bei Commits und speichert die Daten auf `entire/checkpoints/v1`. ([docs.entire.io](https://docs.entire.io/agents/copilot-cli?utm_source=openai)) + +Darum behandelt dieses Skript Rewind/Resume bewusst als **optional** und **fehlertolerant**. Diese Vorsicht ist eine Schlussfolgerung aus den widersprüchlichen Entire-Seiten. ([docs.entire.io](https://docs.entire.io/agents/copilot-cli?utm_source=openai)) + +### `use-scaleway-copilot-debug.sh` +Hilfsskript zum Setzen einer **Scaleway OpenAI-kompatiblen BYOK-Konfiguration** für Copilot CLI. + +Typischer Zweck: + +- Provider-Umgebungsvariablen setzen +- Copilot CLI mit Scaleway testen +- Modellname konfigurieren +- Token-Limits für nicht im Copilot-Katalog enthaltene Modelle explizit setzen +- Debugging eines externen Providers vereinfachen + +Ein typischer Inhalt wäre z. B.: + +- `COPILOT_PROVIDER_TYPE=openai` +- `COPILOT_PROVIDER_BASE_URL=https://api.scaleway.ai/v1` +- `COPILOT_PROVIDER_API_KEY=...` +- `COPILOT_MODEL=qwen3.5-397b-a17b` +- `COPILOT_PROVIDER_MAX_PROMPT_TOKENS=...` +- `COPILOT_PROVIDER_MAX_OUTPUT_TOKENS=...` + +GitHub Copilot CLI BYOK verwendet für OpenAI-kompatible Provider genau dieses Muster aus Base-URL, API-Key und Modell. ([docs.entire.io](https://docs.entire.io/agents/copilot-cli?utm_source=openai)) + +--- + +## Empfohlene Reihenfolge + +### 1. Legacy-Projekt erzeugen + +```bash +./scripts/create-qt5-legacy-benchmark.sh +``` + +### 2. Optional: Copilot mit Scaleway konfigurieren + +```bash +source ./scripts/use-scaleway-copilot-debug.sh +``` + +Danach kannst du prüfen, ob die Variablen gesetzt sind: + +```bash +echo "$COPILOT_PROVIDER_BASE_URL" +echo "$COPILOT_MODEL" +``` + +### 3. Migration mit Entire + Copilot starten + +Standard: + +```bash +./scripts/run-entire-copilot-migration.sh qt5-before/qt5-legacy-migration-benchmark +``` + +Mit optionalem Rewind/Resume: + +```bash +./scripts/run-entire-copilot-migration-with-optional-rewind-resume.sh qt5-before/qt5-legacy-migration-benchmark +``` + +--- + +## Copilot-Prompt für die Migration + +Die Migrationsskripte verwenden sinngemäß einen Prompt in dieser Richtung: + +```text +Migrate the legacy Qt5/C++11 desktop application to Qt6/C++23. + +Requirements: +- Read the legacy source project from qt5-before/qt5-legacy-migration-benchmark +- Write the migrated project into qt6-after/qt6-migrated-benchmark +- Do not modify the legacy source tree in place +- Update CMake from Qt5 to Qt6 +- Keep Core, Gui, and Widgets explicitly linked +- Replace QRegExp with QRegularExpression +- Replace QDesktopWidget / QApplication::desktop() usage with QScreen-based Qt6 APIs +- Remove legacy QTextCodec usage and use modern Qt6-safe text handling +- Modernize old SIGNAL/SLOT syntax to function-pointer connect syntax +- Replace legacy null pointer literals with nullptr +- Preserve the .ui and .qrc integration +- Preserve application behavior +- Upgrade the project from C++11 to C++23 +- For every code change inside a method, add at least three tests covering the changed behavior +- Place all generated tests in qt6-after/qt6-migrated-benchmark/tests +- Update CMake so the migrated application and all tests build successfully with CTest +- Keep the test file naming compatible with repository policy checks +- Briefly explain each migration change +``` + +--- + +## Entire-Hinweise + +Entire erfasst bei unterstützten Agenten u. a.: + +- Gesprächsverlauf +- Dateiveränderungen +- Zeitstempel +- Checkpoints bei Commits +- Session-Kontext auf dem Branch `entire/checkpoints/v1` ([docs.entire.io](https://docs.entire.io/agents/copilot-cli?utm_source=openai)) + +Typischer Entire-Grundablauf: + +```bash +entire agent add copilot-cli +copilot +git add . +git commit -m "..." +entire checkpoint list +``` + +Dieser Ablauf ist direkt in der Copilot-CLI- und Quickstart-Dokumentation beschrieben. ([docs.entire.io](https://docs.entire.io/agents/copilot-cli?utm_source=openai)) + +--- + +## Hinweise zu `use-scaleway-copilot-debug.sh` + +Das Skript sollte **gesourct** werden, nicht als eigener Prozess laufen, damit exportierte Variablen in deiner aktuellen Shell erhalten bleiben: + +```bash +source ./scripts/use-scaleway-copilot-debug.sh +``` + +oder kurz: + +```bash +. ./scripts/use-scaleway-copilot-debug.sh +``` + +Außerdem: + +- keine echten Secrets committen +- API-Keys nicht im Repo speichern +- sensible Werte besser aus bereits gesetzten Umgebungsvariablen übernehmen + +Beispiel: + +```bash +export SCW_SECRET_KEY="..." +source ./scripts/use-scaleway-copilot-debug.sh +``` + +--- + +## Empfohlene Erweiterungen + +Optional kannst du später noch ergänzen: + +- `build-qt6-after.sh` +- `run-ctest-qt6-after.sh` +- `compare-before-after.sh` +- `capture-copilot-env.sh` + +--- + +## Kurzfassung + +Der `scripts/`-Ordner enthält alles, um: + +1. ein Legacy-Qt5-Projekt zu erzeugen, +2. Copilot CLI optional mit Scaleway zu konfigurieren, +3. eine Qt6/C++23-Migration mit Entire zu dokumentieren, +4. und den Migrationslauf reproduzierbar zu machen. diff --git a/scripts/create-qt5-legacy-benchmark.sh b/scripts/create-qt5-legacy-benchmark.sh new file mode 100755 index 0000000..0652275 --- /dev/null +++ b/scripts/create-qt5-legacy-benchmark.sh @@ -0,0 +1,233 @@ +#!/usr/bin/env bash +set -euo pipefail + +TARGET_DIR="${1:-qt5-before/qt5-legacy-migration-benchmark}" +mkdir -p "${TARGET_DIR}" + +cat > "${TARGET_DIR}/CMakeLists.txt" <<'FILE_EOF' +cmake_minimum_required(VERSION 3.10) + +project(Qt5LegacyMigrationBenchmark LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets) + +add_executable(Qt5LegacyMigrationBenchmark + main.cpp + MainWindow.h + MainWindow.cpp + MainWindow.ui + resources.qrc +) + +target_link_libraries(Qt5LegacyMigrationBenchmark + Qt5::Core + Qt5::Gui + Qt5::Widgets +) +FILE_EOF + +cat > "${TARGET_DIR}/MainWindow.h" <<'FILE_EOF' +#pragma once + +#include +#include + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private slots: + void onValidateClicked(); + void onScreenInfoClicked(); + +private: + Ui::MainWindow *ui; + QRegExp legacyPattern; +}; +FILE_EOF + +cat > "${TARGET_DIR}/MainWindow.cpp" <<'FILE_EOF' +#include "MainWindow.h" +#include "ui_MainWindow.h" + +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , legacyPattern("^[A-Za-z]+\\d+$") +{ + ui->setupUi(this); + + QTextCodec *codec = QTextCodec::codecForName("UTF-8"); + QString title = codec->toUnicode("Qt5 Legacy Migration Benchmark"); + setWindowTitle(title); + + ui->label->setText(codec->toUnicode("Bitte Text wie Test123 eingeben.")); + ui->iconLabel->setPixmap(QPixmap(":/icons/app.png").scaled(48, 48)); + + connect(ui->validateButton, SIGNAL(clicked()), this, SLOT(onValidateClicked())); + connect(ui->screenButton, SIGNAL(clicked()), this, SLOT(onScreenInfoClicked())); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::onValidateClicked() +{ + QString text = ui->lineEdit->text(); + + if (legacyPattern.exactMatch(text)) { + ui->resultLabel->setText("Match"); + } else { + ui->resultLabel->setText("No match"); + } +} + +void MainWindow::onScreenInfoClicked() +{ + QRect screenGeometry = QApplication::desktop()->screenGeometry(); + QString info = QString("Screen: %1x%2") + .arg(screenGeometry.width()) + .arg(screenGeometry.height()); + + ui->resultLabel->setText(info); +} +FILE_EOF + +cat > "${TARGET_DIR}/main.cpp" <<'FILE_EOF' +#include +#include +#include "MainWindow.h" + +int main(int argc, char *argv[]) +{ +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); +#endif + + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} +FILE_EOF + +cat > "${TARGET_DIR}/MainWindow.ui" <<'FILE_EOF' + + + MainWindow + + + + 0 + 0 + 420 + 240 + + + + Qt5 Legacy App + + + + + + + Initial text + + + + + + + Enter value like Test123 + + + + + + + + + + + + + + + + Validate + + + + + + + Screen Info + + + + + + + + + Result appears here + + + + + + + + + +FILE_EOF + +cat > "${TARGET_DIR}/resources.qrc" <<'FILE_EOF' + + + app.png + + +FILE_EOF + +( + cd "${TARGET_DIR}" + python3 - <<'FILE_EOF' +import base64 +png = base64.b64decode( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Wn8nKsAAAAASUVORK5CYII=" +) +with open("app.png", "wb") as f: + f.write(png) +FILE_EOF +) + +echo "Qt5 legacy benchmark created at: ${TARGET_DIR}" diff --git a/scripts/run-entire-copilot-migration-with-optional-rewind-resume.sh b/scripts/run-entire-copilot-migration-with-optional-rewind-resume.sh new file mode 100755 index 0000000..955d491 --- /dev/null +++ b/scripts/run-entire-copilot-migration-with-optional-rewind-resume.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -euo pipefail + +PROJECT_DIR="${1:-qt5-before/qt5-legacy-migration-benchmark}" + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { + echo "ERROR: '$1' not found in PATH." >&2 + exit 1 + } +} + +need_cmd entire +need_cmd copilot +need_cmd git + +if [ ! -d "$PROJECT_DIR" ]; then + echo "ERROR: project directory '$PROJECT_DIR' not found." >&2 + exit 1 +fi + +cd "$PROJECT_DIR" + +if [ ! -d .git ]; then + git init +fi + +entire agent add copilot-cli +entire status + +git add . +if git diff --cached --quiet; then + git commit --allow-empty -m "baseline: Qt5/C++11 legacy benchmark before Qt6/C++23 migration" +else + git commit -m "baseline: Qt5/C++11 legacy benchmark before Qt6/C++23 migration" +fi + +entire checkpoint list + +cat <<'PROMPT' + +Use this prompt in Copilot CLI: + +Migrate the legacy Qt5/C++11 desktop application to Qt6/C++23. + +Requirements: +- Read the legacy source project from qt5-before/qt5-legacy-migration-benchmark +- Write the migrated project into qt6-after/qt6-migrated-benchmark +- Do not modify the legacy source tree in place +- Update CMake from Qt5 to Qt6 +- Keep Core, Gui, and Widgets explicitly linked +- Replace QRegExp with QRegularExpression +- Replace QDesktopWidget / QApplication::desktop() usage with QScreen-based Qt6 APIs +- Remove legacy QTextCodec usage and use modern Qt6-safe text handling +- Modernize old SIGNAL/SLOT syntax to function-pointer connect syntax +- Replace legacy null pointer literals with nullptr +- Preserve the .ui and .qrc integration +- Preserve application behavior +- Upgrade the project from C++11 to C++23 +- For every code change inside a method, add at least three tests covering the changed behavior +- Place all generated tests in qt6-after/qt6-migrated-benchmark/tests +- Update CMake so the migrated application and all tests build successfully with CTest +- Keep the test file naming compatible with repository policy checks +- Briefly explain each migration change +PROMPT + +copilot + +git status --short +git add . + +if git diff --cached --quiet; then + echo "No changes to commit." +else + git commit -m "migrate: Qt5/C++11 app to Qt6/C++23 with tests and modern Qt APIs" +fi + +entire checkpoint list +entire checkpoint explain HEAD || true + +read -r -p "Optional Rewind testen? [y/N] " DO_REWIND +if [[ "${DO_REWIND:-N}" =~ ^[Yy]$ ]]; then + if ! entire checkpoint rewind; then + echo "WARN: rewind not supported or failed." + fi +fi + +read -r -p "Optional Session-Resume-Hinweis ausgeben? [y/N] " DO_RESUME +if [[ "${DO_RESUME:-N}" =~ ^[Yy]$ ]]; then + CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)" + if ! entire session resume "${CURRENT_BRANCH}"; then + echo "WARN: resume not supported or failed." + fi +fi diff --git a/scripts/run-entire-copilot-migration.sh b/scripts/run-entire-copilot-migration.sh new file mode 100755 index 0000000..085dc03 --- /dev/null +++ b/scripts/run-entire-copilot-migration.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -euo pipefail + +PROJECT_DIR="${1:-qt5-before/qt5-legacy-migration-benchmark}" + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { + echo "ERROR: '$1' not found in PATH." >&2 + exit 1 + } +} + +need_cmd entire +need_cmd copilot +need_cmd git + +if [ ! -d "$PROJECT_DIR" ]; then + echo "ERROR: project directory '$PROJECT_DIR' not found." >&2 + exit 1 +fi + +cd "$PROJECT_DIR" + +if [ ! -d .git ]; then + git init +fi + +entire agent add copilot-cli +entire status + +git add . +if git diff --cached --quiet; then + git commit --allow-empty -m "baseline: Qt5/C++11 legacy benchmark before Qt6/C++23 migration" +else + git commit -m "baseline: Qt5/C++11 legacy benchmark before Qt6/C++23 migration" +fi + +entire checkpoint list + +cat <<'PROMPT' + +Use this prompt in Copilot CLI: + +Migrate the legacy Qt5/C++11 desktop application to Qt6/C++23. + +Requirements: +- Read the legacy source project from qt5-before/qt5-legacy-migration-benchmark +- Write the migrated project into qt6-after/qt6-migrated-benchmark +- Do not modify the legacy source tree in place +- Update CMake from Qt5 to Qt6 +- Keep Core, Gui, and Widgets explicitly linked +- Replace QRegExp with QRegularExpression +- Replace QDesktopWidget / QApplication::desktop() usage with QScreen-based Qt6 APIs +- Remove legacy QTextCodec usage and use modern Qt6-safe text handling +- Modernize old SIGNAL/SLOT syntax to function-pointer connect syntax +- Replace legacy null pointer literals with nullptr +- Preserve the .ui and .qrc integration +- Preserve application behavior +- Upgrade the project from C++11 to C++23 +- For every code change inside a method, add at least three tests covering the changed behavior +- Place all generated tests in qt6-after/qt6-migrated-benchmark/tests +- Update CMake so the migrated application and all tests build successfully with CTest +- Keep the test file naming compatible with repository policy checks +- Briefly explain each migration change +PROMPT + +copilot + +git status --short +git add . + +if git diff --cached --quiet; then + echo "No changes to commit." + exit 0 +fi + +git commit -m "migrate: Qt5/C++11 app to Qt6/C++23 with tests and modern Qt APIs" +entire checkpoint list +entire checkpoint explain HEAD || true diff --git a/scripts/use-scaleway-copilot-debug.sh b/scripts/use-scaleway-copilot-debug.sh new file mode 100755 index 0000000..a35b156 --- /dev/null +++ b/scripts/use-scaleway-copilot-debug.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +set -euo pipefail + +red() { printf "\033[31m%s\033[0m\n" "$*"; } +green() { printf "\033[32m%s\033[0m\n" "$*"; } +yellow() { printf "\033[33m%s\033[0m\n" "$*"; } +blue() { printf "\033[34m%s\033[0m\n" "$*"; } + +fail() { + red "FEHLER: $*" + exit 1 +} + +check_cmd() { + command -v "$1" >/dev/null 2>&1 || fail "'$1' nicht gefunden" +} + +mask() { + local s="${1:-}" + local n=${#s} + if [ "$n" -le 8 ]; then + printf '********' + else + printf '%s****%s' "${s:0:4}" "${s: -4}" + fi +} + +check_env() { + [ -n "${COPILOT_PROVIDER_TYPE:-}" ] || fail "COPILOT_PROVIDER_TYPE fehlt" + [ -n "${COPILOT_PROVIDER_API_KEY:-}" ] || fail "COPILOT_PROVIDER_API_KEY fehlt" + [ -n "${COPILOT_PROVIDER_BASE_URL:-}" ] || fail "COPILOT_PROVIDER_BASE_URL fehlt" + [ -n "${COPILOT_MODEL:-}" ] || fail "COPILOT_MODEL fehlt" +} + +print_env_summary() { + blue "== Konfiguration ==" + echo "Provider : ${COPILOT_PROVIDER_TYPE}" + echo "Base URL : ${COPILOT_PROVIDER_BASE_URL}" + echo "Modell : ${COPILOT_MODEL}" + echo "API Key : $(mask "${COPILOT_PROVIDER_API_KEY}")" + echo +} + +probe_models() { + blue "== Prüfe Models-Endpoint ==" + local models_url="${COPILOT_PROVIDER_BASE_URL%/}/models" + + local http_code + http_code="$(curl -sS -o /tmp/copilot_models_probe.json \ + -w "%{http_code}" \ + -H "Authorization: Bearer ${COPILOT_PROVIDER_API_KEY}" \ + "$models_url" || true)" + + echo "GET $models_url" + echo "HTTP $http_code" + + if [ "$http_code" = "200" ]; then + green "Models-Endpoint erreichbar" + if command -v jq >/dev/null 2>&1; then + echo + yellow "Gefundene Modelle (erste 20):" + jq -r ' + if .data then .data[]?.id + elif type=="array" then .[]?.id + else empty end + ' /tmp/copilot_models_probe.json | head -20 + echo + if jq -e --arg model "${COPILOT_MODEL}" ' + if .data then any(.data[]?; .id == $model) + elif type=="array" then any(.[]?; .id == $model) + else false end + ' /tmp/copilot_models_probe.json >/dev/null 2>&1; then + green "Modell '${COPILOT_MODEL}' im Models-Endpoint gefunden" + else + yellow "Modell '${COPILOT_MODEL}' nicht eindeutig im Models-Endpoint gefunden" + fi + else + yellow "jq nicht installiert; JSON-Antwort nicht geparst" + fi + else + red "Models-Endpoint nicht erfolgreich erreichbar" + echo + sed -n '1,120p' /tmp/copilot_models_probe.json || true + echo + fi +} + +probe_chat() { + blue "== Prüfe Chat-Completions minimal ==" + local chat_url="${COPILOT_PROVIDER_BASE_URL%/}/chat/completions" + + cat >/tmp/copilot_chat_probe.json </dev/null 2>&1; then + echo + yellow "Antwortauszug:" + jq -r '.choices[0].message.content // .choices[0].delta.content // "keine textantwort gefunden"' \ + /tmp/copilot_chat_probe_response.json + echo + else + sed -n '1,80p' /tmp/copilot_chat_probe_response.json || true + fi + else + red "Chat-Completions fehlgeschlagen" + echo + sed -n '1,160p' /tmp/copilot_chat_probe_response.json || true + echo + fi +} + +run_copilot() { + blue "== Starte Copilot CLI ==" + exec copilot "$@" +} + +main() { + check_cmd curl + check_cmd copilot + check_env + print_env_summary + probe_models + echo + probe_chat + echo + run_copilot "$@" +} + +main "$@" \ No newline at end of file