Neler yeni

Foruma hoş geldin, Ziyaretçi

Metin2Lobby.com Metin2 Private Server Tanıtım Advertising Ve Geliştirme Forumudur.Metin2 pvp serverler,1-99,1-105,1-120,55-120 global serverları paylaş yada ara.
Forum içeriğine ve tüm hizmetlerimize erişim sağlamak için foruma kayıt olmalı ya da giriş yapmalısınız. Foruma üye olmak tamamen ücretsizdir.

[Dev Paylaşım] MySQL mysqlclient.lib Derleme Sorununa Son! - MySQL Builder Pro

Admin

Metin2Lobby
Yönetici
Founder
Katılım
6 Mayıs 2022
Konular
48,291
Mesajlar
48,601
Tepkime puanı
75
M2 Yaşı
3 yıl 11 ay 10 gün
Trophy Puan
48
Konum
Web sitesi
M2 Yang
488,879
Ticaret : 1 / 0 / 0
Ticaret Oranı : 100%
Merhaba TurkMMO Ailesi,

Metin2 sunucu dosyalarını Windows üzerinde derlerken hepimizin kabusu olan bir adıma kesin çözüm getiren bir araçla karşınızdayım: MySQL mysqlclient.lib kütüphanesini derlemek.

Artık saatlerce süren denemeler, anlamsız CMake hataları, eksik OpenSSL bağımlılıkları ve uyumsuz Visual Studio sürümleriyle uğraşmaya son! Bu araç, tüm bu karmaşık süreci sizin için tek tıkla hallediyor.

MySQL Builder Pro Nedir?

Bu program, MySQL kaynak kodunu (özellikle Metin2 için gerekli olan mysqlclient.lib dosyasını) Windows ortamında derleme işlemini otomatikleştiren, grafik arayüzlü (GUI) bir araçtır.

Ana Özellikleri:

  • <li data-xf-list-type="ul">Tam Otomasyon: Git, CMake, Ninja, Perl gibi gerekli tüm araçları kendisi indirir ve kurar. <li data-xf-list-type="ul">Grafik Arayüz: Tüm ayarları (Release/Debug, MT/MD, MySQL sürümü) kolayca seçmenizi sağlar. <li data-xf-list-type="ul">OpenSSL Desteği: Derleme için zorunlu olan OpenSSL kütüphanesini kaynak kodundan sizin için derler ve yapılandırır. <li data-xf-list-type="ul">Hata Kontrolü: Süreci anlık olarak log ekranından takip edebilir, olası hataları anında görebilirsiniz. <li data-xf-list-type="ul">Esneklik: Hem statik (/MT - redist paketi gerektirmez) hem de dinamik (/MD) runtime seçenekleriyle derleme yapabilirsiniz.



1. Gerekli Kurulumlar (Programa Geçmeden Önce Mutlaka Yapılmalı!)



Bu adımları eksiksiz yapmanız, programın sorunsuz çalışması için kritiktir.

A. Visual Studio 2022 Kurulumu

Derleme işlemleri için Microsoft'un C++ derleyicisi zorunludur.

  1. <li data-xf-list-type="ol">İndirme: linkinden "Community" sürümünü indirin. <li data-xf-list-type="ol">Kurulum: İndirdiğiniz yükleyiciyi çalıştırın. Karşınıza gelen "İş Yükleri" (Workloads) ekranında mutlaka aşağıdaki seçeneği işaretleyin:
    • <li data-xf-list-type="ul">C++ ile masaüstü geliştirme (Desktop development with C++) <li data-xf-list-type="ul">Bu seçeneği işaretlemezseniz program derleme yapamaz! Kurulum tamamlandıktan sonra başka bir işlem yapmanıza gerek yok.
B. Python Kurulumu

Paylaştığım bu araç, Python ile yazılmıştır. Çalıştırmak için sisteminizde Python kurulu olmalıdır.

  1. <li data-xf-list-type="ol">İndirme: linkinden en güncel Python sürümünü (örn: 3.12.x) indirin. <li data-xf-list-type="ol">Kurulum: Yükleyiciyi çalıştırın. ÇOK ÖNEMLİ: Kurulumun ilk ekranında, altta bulunan "Add Python.exe to PATH" kutucuğunu mutlaka işaretleyin. Bu adımı atlarsanız program çalışmaz.
C. Gerekli Python Paketleri

  1. <li data-xf-list-type="ol">Windows'ta Başlat menüsünü açın. <li data-xf-list-type="ol">cmd yazarak "Komut İstemi"ni (Command Prompt) çalıştırın. <li data-xf-list-type="ol">Açılan siyah ekrana aşağıdaki komutu kopyalayıp yapıştırın ve Enter'a basın:
    Kod:
    pip install requests ttkbootstrap psutil

    Bu komut, programın ihtiyaç duyduğu ek kütüphaneleri otomatik olarak yükleyecektir.



2. MySQL Builder Pro Kullanımı



Tüm ön kurulumları tamamladıysanız, artık programı kullanmaya hazırsınız.

Adım 1: Programı Çalıştırma

Aşağıda paylaştığım kaynak kodunun tamamını kopyalayın. Masaüstünde yeni bir metin belgesi oluşturun, içine yapıştırın ve adını mysql_builder.py olarak kaydedin. Daha sonra bu dosyaya çift tıklayarak çalıştırın.

Adım 2: Gerekli Derleme Araçlarını Kurma (İlk Kullanım)

Program açıldığında, "Araç Durumu" panelinde birçok aracın "❌ Yok" olarak göründüğünü fark edeceksiniz.

  1. <li data-xf-list-type="ol">Sağ taraftaki "Eksikleri Kur" butonuna tıklayın. <li data-xf-list-type="ol">Program, CMake, Git, Ninja gibi tüm gerekli araçları otomatik olarak programın bulunduğu dizine .tools adında bir klasör açıp içine indirecektir. Bu işlem internet hızınıza bağlı olarak birkaç dakika sürebilir. <li data-xf-list-type="ol">İşlem bittiğinde tablo güncellenecek ve tüm araçlar "✔️ Yüklü" olarak görünecektir.
Adım 3: OpenSSL Kütüphanesini Derleme (İlk Kullanım)

MySQL derlemesi için OpenSSL kütüphanesi zorunludur.

  1. <li data-xf-list-type="ol">"OpenSSL Durumu" paneline bakın. <li data-xf-list-type="ol">Sağdaki "OpenSSL Kur (R+D)" butonuna tıklayın. <li data-xf-list-type="ol">Program, OpenSSL'i hem Release hem de Debug modları için derleyecektir. Bu işlem de biraz zaman alabilir. Bittiğinde durum tablosu "✔️ OK" olarak güncellenecektir.
Adım 4: MySQL Derleme Ayarlarını Yapılandırma

Artık her şey hazır olduğuna göre, hangi mysqlclient.lib dosyasını istediğimizi seçeceğiz.

  • <li data-xf-list-type="ul">MySQL Sürümü: Metin2 sunucu dosyalarınızla uyumlu olan MySQL sürümünü seçin. Genellikle mysql-5.x veya mysql-8.x sürümleri kullanılır. "Listele" butonu güncel sürümleri çeker. <li data-xf-list-type="ul">Runtime (ÇOK ÖNEMLİ):
    • <li data-xf-list-type="ul">MT: Statik derleme yapar. Oluşturulan .lib dosyası, C++ runtime kütüphanelerini içinde barındırır. Bu sayede sunucunuza vcredist.exe gibi ek paketler kurmanıza gerek kalmaz. Genellikle en sorunsuz ve tavsiye edilen seçenektir. <li data-xf-list-type="ul">MD: Dinamik derleme yapar. Bu seçeneği kullanırsanız, oyun sunucunuzu çalıştıracağınız makinede uygun Visual C++ Redistributable paketinin kurulu olması gerekir.
    <li data-xf-list-type="ul">Seçenekler:
    • <li data-xf-list-type="ul">Release Derle: Canlı sunucular için kullanacağınız optimize edilmiş .lib dosyasını oluşturur. (İşaretli kalmalı) <li data-xf-list-type="ul">Debug Derle: Hata ayıklama modunda derleme yapar. Geliştiriciler için gereklidir. (İsteğe bağlı) <li data-xf-list-type="ul">Yalnız client kütüphanesi: Bu seçenek mutlaka işaretli kalmalıdır! Amacımız zaten sadece mysqlclient.lib derlemek.
Adım 5: Derlemeyi Başlatma ve Sonuçları Alma

  1. <li data-xf-list-type="ol">Tüm ayarları yaptıktan sonra sağ alttaki yeşil "Başlat" butonuna tıklayın. <li data-xf-list-type="ol">Program, seçtiğiniz MySQL sürümünü indirip derlemeye başlayacaktır. Süreci log ekranından ve ilerleme çubuklarından takip edebilirsiniz. <li data-xf-list-type="ol">Derleme başarıyla tamamlandığında bir bildirim alacaksınız. <li data-xf-list-type="ol">"Install Klasörünü Aç" butonuna tıklayın. <li data-xf-list-type="ol">Karşınıza gelen install klasörünün içinde, yaptığınız seçimlere göre (örn: MT/mysql-8.0.38/release/lib/) mysqlclient.lib dosyanızı bulabilirsiniz.
Artık bu .lib dosyasını alıp sunucu dosyalarınızın kaynak kodunda extern/lib klasörüne atarak derlemelerinizi sorunsuzca yapabilirsiniz.




3. Programın Kaynak Kodu


Kod:
# -*- coding: utf-8 -*- import os import re import sys import time import shutil import uuid import zipfile import threading import queue import subprocess import tarfile import contextlib import json import datetime import traceback from pathlib import Path from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass from enum import Enum from typing import Callable, Any, cast try:     import requests     import psutil     import ttkbootstrap as ttk     import tkinter as tk     from ttkbootstrap.constants import *     from tkinter import font as tkFont, messagebox, scrolledtext, filedialog except ImportError:     raise SystemExit("Gerekli paketler eksik. Lütfen çalıştırın: pip install requests ttkbootstrap psutil") class C:     class BuildType(str, Enum):         RELEASE = "Release"         DEBUG = "Debug"         RELWITHDEBINFO = "RelWithDebInfo"     class Runtime(str, Enum):         MT = "MT"         MD = "MD"     class ToolMode(str, Enum):         BUNDLED = "bundled"         SYSTEM = "system"     class Generator(str, Enum):         NINJA = "Ninja"         VS17 = "VS17"     class LogLevel(str, Enum):         STAGE = "stage"         CMD = "cmd"         WARN = "warn"         ERR = "err"         INFO = "info"     ROOT = Path.cwd()     LOGS = ROOT / "logs"     TOOLS = ROOT / ".tools"     DL = ROOT / "downloads"     SRC = ROOT / "src"     BUILD = ROOT / "build"     INST = ROOT / "install"     DEPS = ROOT / ".deps"     TRASH = ROOT / ".trash"     OPENSSL_BASE = DEPS / "openssl"     OPENSSL_SRC = DEPS / "openssl-src"     CONFIG_FILE = ROOT / "config.json"     DEFAULT_JOBS = max(1, (os.cpu_count() or 4) - 2)     DISK_MIN_GB = 5.0     MAX_LOG_LINES = 5000     _RE_TAG = re.compile(r"^mysql-\d+\.\d+(?:\.\d+)?$")     _RE_NINJA = re.compile(r"\[(\d+)\s*/\s*(\d+)\]")     _RE_ERR_MULTI_RULE = re.compile(r"multiple rules generate", re.I)     URLS = {         "7zr": "https://www.7-zip.org/a/7zr.exe", "7zextra": "https://www.7-zip.org/a/7z2408-extra.7z",         "mingit": "https://github.com/git-for-windows/git/releases/download/v2.46.0.windows.1/MinGit-2.46.0-64-bit.zip",         "cmake": "https://github.com/Kitware/CMake/releases/download/v3.30.3/cmake-3.30.3-windows-x86_64.zip",         "ninja": "https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-win.zip",         "perl": "https://strawberryperl.com/download/5.32.1.1/strawberry-perl-5.32.1.1-64bit-portable.zip",         "nasm": "https://www.nasm.us/pub/nasm/releasebuilds/2.16.03/win64/nasm-2.16.03-win64.zip",         "openssl": "https://github.com/openssl/openssl/archive/refs/tags/openssl-3.3.1.zip",         "sccache": "https://github.com/mozilla/sccache/releases/download/v0.8.0/sccache-v0.8.0-x86_64-pc-windows-msvc.tar.gz",     }     BUNDLED_PATHS = [TOOLS, TOOLS / "7zip", TOOLS / "cmake" / "bin", TOOLS / "mingit" / "cmd",                      TOOLS / "perl" / "perl" / "bin", TOOLS / "perl" / "c" / "bin", TOOLS / "nasm"] @dataclass class BuildConfig:     build_type: C.BuildType     source_dir: Path     tag: str     tool_mode: C.ToolMode     generator: C.Generator     runtime: C.Runtime     cxx_version: str     jobs: int     extra_defs: str     only_client: bool     @property     def build_dir(self) -&gt; Path: return C.BUILD / self.runtime.value / self.tag / self.build_type.value.lower().replace(' ', '_')     @property     def install_dir(self) -&gt; Path: return C.INST / self.runtime.value / self.tag / self.build_type.value.lower().replace(' ', '_')     @property     def ssl_root_dir(self) -&gt; Path: return self._openssl_root(self.runtime, self.build_type)     @staticmethod     def _openssl_root(runtime: C.Runtime, build_type: C.BuildType) -&gt; Path:         is_debug = build_type == C.BuildType.DEBUG         base = C.OPENSSL_BASE / runtime.value         root = base / ("debug" if is_debug else "release")         root.mkdir(parents=True, exist_ok=True)         return root class Bus:     def __init__(self, app: 'App'): self.app = app; self.q = app.q     def log(self, message: str, level: C.LogLevel = C.LogLevel.INFO) -&gt; None: self.q.put(("log", message, level.value))     def progress(self, kind: str, pc: int, text: str = "") -&gt; None: self.q.put((kind, pc, text))     def status(self, text: str) -&gt; None: self.q.put(("status", text))     def update_task_progress(self, task_id: str, progress_text: str) -&gt; None: self.q.put(("task_progress", task_id, progress_text)) @contextlib.contextmanager def temp_env_path(extra_paths: list[Path]):     old_path = os.environ.get("PATH", "")     new_path_parts = [str(p) for p in extra_paths if p.exists()]     new_path_parts.append(old_path)     new_path = ";".join(new_path_parts)     try:         os.environ["PATH"] = new_path         yield     finally:         os.environ["PATH"] = old_path def run_out(cmd: list[str] | str, cwd: Path | None = None) -&gt; str:     try:         return subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True, errors="replace",                                      shell=isinstance(cmd, str), creationflags=subprocess.CREATE_NO_WINDOW,                                      cwd=cwd, timeout=15).strip()     except subprocess.TimeoutExpired:         return "ERR:Timeout"     except Exception as e:         return f"ERR:{e}" def stream_proc(cmd: list[str] | str, env: dict, on_line: Callable[[str], None], log_file_path: Path | None = None,                 low_pri: bool = True, stop_event: threading.Event | None = None, cwd: str | Path | None = None,                 on_start: Callable[[subprocess.Popen], None] | None = None) -&gt; bool:     log_file = None     try:         if log_file_path:             log_file_path.parent.mkdir(parents=True, exist_ok=True)             log_file = open(log_file_path, "w", encoding="utf-8", errors="replace")         creation_flags = 0         if os.name == "nt" and low_pri:             creation_flags |= getattr(subprocess, "BELOW_NORMAL_PRIORITY_CLASS", 0)             creation_flags |= getattr(subprocess, "CREATE_NO_WINDOW", 0)         p = subprocess.Popen(cmd, shell=isinstance(cmd, str), stdout=subprocess.PIPE, stderr=subprocess.STDOUT,                              universal_newlines=True, encoding="utf-8", errors="replace", bufsize=1,                              env=env, creationflags=creation_flags, cwd=cwd)                  if on_start:             on_start(p)                  for line in iter(p.stdout.readline, ''):             if stop_event and stop_event.is_set():                 p.terminate()                 on_line("--- Process terminated by user request. ---")                 break             if line:                 clean_line = line.rstrip()                 on_line(clean_line)                 if log_file:                     log_file.write(clean_line + '\n')                  return p.wait() == 0     finally:         if log_file:             log_file.close() class SevenZipProgressParser:     _RE_PERCENT = re.compile(r"(\d+)\s*\%")     def __init__(self, bus: Bus): self.bus = bus; self.last_progress_update = -1     def parse_line(self, line: str) -&gt; None:         match = self._RE_PERCENT.search(line)         if match:             pc = int(match.group(1))             if pc &gt; self.last_progress_update:                 self.bus.progress("work", pc, f"%{pc} çıkarılıyor...")                 self.last_progress_update = pc                 if pc % 10 == 0:                     self.bus.log(line, C.LogLevel.CMD)         else:             self.bus.log(line, C.LogLevel.CMD) class TaskManager:     def __init__(self, app: 'App'):         self.app = app         self.bus = app.bus         self.task_registry: dict[str, dict[str, Any]] = {}     def register(self, name: str, target: Callable, args: tuple = (), kwargs: dict | None = None,                  stop_event: threading.Event | None = None, is_build_task: bool = True,                  task_id: str | None = None) -&gt; threading.Thread:         task_id = task_id or f"task_{uuid.uuid4().hex[:8]}"         kwargs = kwargs or {}         if "task_id" in target.__code__.co_varnames:             kwargs['task_id'] = task_id         thread = threading.Thread(target=target, args=args, kwargs=kwargs, daemon=True, name=task_id)         self.task_registry[task_id] = {'thread': thread, 'name': name, 'start_time': time.time(),                                        'stop_event': stop_event, 'is_build_task': is_build_task,                                        'progress': 'Başlatılıyor...', 'status': 'Başlatılıyor', 'success': None}         thread.start()         self.bus.log(f"Yeni görev başlatıldı: {name} (ID: {task_id})", C.LogLevel.INFO)         return thread     def update_monitor(self) -&gt; None:         task_tree = self.app.ui_manager.task_tree         finished_tasks = {tid: info for tid, info in self.task_registry.items() if not info['thread'].is_alive()}         running_tasks = {tid: info for tid, info in self.task_registry.items() if info['thread'].is_alive()}         for task_id, info in finished_tasks.items():             if info.get('status') == 'Bitti':                 continue                          self.task_registry[task_id]['status'] = 'Bitti'             if task_tree.exists(task_id):                 final_status = "Başarısız" if info.get('success') is False else "Bitti"                 task_tree.set(task_id, "status", final_status)                 task_tree.set(task_id, "progress", "Tamamlandı")                 if final_status == "Başarısız":                     task_tree.item(task_id, tags=("failed",))                 if not info.get('is_build_task'):                     self.app.after(5000, lambda tid=task_id: task_tree.delete(tid) if task_tree.exists(tid) else None)                  for task_id, info in running_tasks.items():             elapsed = time.time() - info['start_time']             runtime_str = str(datetime.timedelta(seconds=int(elapsed)))             status = "Durduruluyor" if info.get('stop_event') and info['stop_event'].is_set() else "Çalışıyor"             values = (info['name'], status, info.get('progress', '...'), runtime_str)             if task_tree.exists(task_id):                 task_tree.item(task_id, values=values)             else:                 task_tree.insert("", "end", iid=task_id, values=values)         task_tree.tag_configure("failed", background="#f8d7da", foreground="#842029")         self.app.after(1000, self.update_monitor)     def terminate_selected_task(self) -&gt; None:         task_tree = self.app.ui_manager.task_tree         selected_items = task_tree.selection()         if not selected_items:             messagebox.showwarning("Uyarı", "Lütfen durdurmak için bir görev seçin.")             return         task_id = selected_items[0]         if task_id in self.task_registry:             task_info = self.task_registry[task_id]             if task_info['thread'].is_alive():                 if task_info.get('stop_event'):                     task_info['stop_event'].set()                     self.bus.log(f"Durdurma sinyali gönderildi: {task_info['name']}", C.LogLevel.WARN)                     messagebox.showinfo("Başarılı", f"'{task_info['name']}' görevine durdurma sinyali gönderildi.")                 else:                     messagebox.showerror("Hata", "Bu görev, güvenli bir şekilde durdurulmayı desteklemiyor.")             else:                 messagebox.showwarning("Bilgi", "Seçilen görev artık çalışmıyor veya tamamlanmış.")     def stop_all(self) -&gt; None:         self.bus.log("DURDURMA KOMUTU GÖNDERİLDİ! Tüm aktif görevler durduruluyor.", C.LogLevel.ERR)         for task_info in self.task_registry.values():             if task_info['thread'].is_alive() and task_info.get('stop_event'):                 task_info['stop_event'].set()                 self.bus.log(f"GÖREV DURDURULDU: {task_info['name']}", C.LogLevel.WARN)                  active_pids = self.app.build_manager.get_active_pids()         if active_pids:             self.bus.log(f"Sonlandırılacak {len(active_pids)} alt süreç bulundu.", C.LogLevel.ERR)             for pid in active_pids:                 try:                     proc = psutil.Process(pid)                     proc.kill()                     self.bus.log(f"Süreç {pid} ({proc.name()}) başarıyla sonlandırıldı.", C.LogLevel.WARN)                 except psutil.NoSuchProcess:                     pass                 except psutil.Error as e:                     self.bus.log(f"Süreç {pid} sonlandırılamadı: {e}", C.LogLevel.ERR)         self.app.build_manager.clear_active_pids()         self.app.set_busy(False) class UIManager:     def __init__(self, app: 'App'):         self.app = app         self.root = app         self.bus = app.bus         self.control_widgets: list[tk.Widget | ttk.Variable] = []         self.log_filter_vars: dict[str, tk.BooleanVar] = {}         self.last_tag_from_config = None         self._create_main_layout()         self._create_config_widgets(self.top_frame)         self._create_log_widgets(self.log_frame)         self._create_task_panel(self.task_frame)         self.load_settings()         self.last_disk_io = psutil.disk_io_counters()         self.last_update_time = time.time()              def _create_main_layout(self) -&gt; None:         main_pane = ttk.PanedWindow(self.root, orient=VERTICAL)         main_pane.pack(fill=BOTH, expand=True, padx=10, pady=10)         self.top_frame = ttk.Frame(main_pane, padding=5)         main_pane.add(self.top_frame, weight=0)         bottom_pane = ttk.PanedWindow(main_pane, orient=HORIZONTAL)         main_pane.add(bottom_pane, weight=1)         self.log_frame = ttk.LabelFrame(bottom_pane, text="Log", padding=5)         bottom_pane.add(self.log_frame, weight=1)         self.task_frame = ttk.LabelFrame(bottom_pane, text="Arka Plan Görev Yöneticisi", padding=5)         bottom_pane.add(self.task_frame, weight=0)          def _register_control(self, widget: tk.Widget | ttk.Variable) -&gt; Any:         self.control_widgets.append(widget)         return widget              def _create_config_widgets(self, parent: ttk.Frame) -&gt; None:         parent.grid_columnconfigure(0, weight=1)         build_panel = ttk.LabelFrame(parent, text="Derleme Ayarları", padding=10)         build_panel.grid(row=0, column=0, sticky="ew")         info_panels_frame = ttk.Frame(parent)         info_panels_frame.grid(row=1, column=0, sticky="ew")         info_panels_frame.grid_columnconfigure(0, weight=1)         info_panels_frame.grid_columnconfigure(1, weight=1)         dash = ttk.LabelFrame(parent, text="İşlem Durumu", padding=10)         dash.grid(row=2, column=0, sticky="ew", pady=(10, 0))         self._create_build_panel_content(build_panel)         self._create_tools_panel(info_panels_frame)         self._create_ssl_panel(info_panels_frame)         self._create_status_panel_content(dash)     def _create_tools_panel(self, parent: ttk.Frame) -&gt; None:         tools_frame = ttk.LabelFrame(parent, text="Araç Durumu", padding=10)         tools_frame.grid(row=0, column=0, sticky="nsew", padx=(0, 5))         tools_frame.grid_columnconfigure(0, weight=1)         self.tree = ttk.Treeview(tools_frame, columns=("tool", "status", "ver", "src", "path"), show="headings", height=6, bootstyle=INFO)         for c, t, w in (("tool","Araç",100), ("status","Durum",80), ("ver","Sürüm",100), ("src","Kaynak",70), ("path","Yol",200)):             self.tree.heading(c, text=t)             self.tree.column(c, width=w, anchor=W)         self.tree.grid(row=0, column=0, sticky="nsew")         ysb = ttk.Scrollbar(tools_frame, orient="vertical", command=self.tree.yview)         ysb.grid(row=0, column=1, sticky="ns")         self.tree.configure(yscrollcommand=ysb.set)         btn_frame = ttk.Frame(tools_frame)         btn_frame.grid(row=0, column=2, sticky="ns", padx=(10, 0))         ttk.Button(btn_frame, text="Yenile", command=self.app.tool_manager.refresh_tools, width=20, bootstyle=SECONDARY).pack(fill=X, pady=2)         ttk.Button(btn_frame, text="Eksikleri Kur", command=lambda: self.app.tool_manager.install_tools_async(missing=True), width=20).pack(fill=X, pady=2)         ttk.Button(btn_frame, text="Hepsini Yeniden Kur", command=self.app.tool_manager.confirm_reinstall_all, width=20, bootstyle=OUTLINE).pack(fill=X, pady=2)     def _create_ssl_panel(self, parent: ttk.Frame) -&gt; None:         ssl_frame = ttk.LabelFrame(parent, text="OpenSSL Durumu", padding=10)         ssl_frame.grid(row=0, column=1, sticky="nsew", padx=(5, 0))         ssl_frame.grid_columnconfigure(0, weight=1)         self.ssl_tree = ttk.Treeview(ssl_frame, columns=("component", "status"), show="headings", height=6, bootstyle=INFO)         self.ssl_tree.heading("component", text="Yapılandırma")         self.ssl_tree.column("component", width=250, anchor=W)         self.ssl_tree.heading("status", text="Durum")         self.ssl_tree.column("status", width=100, anchor=W)         self.ssl_tree.grid(row=0, column=0, sticky="nsew")         btn_frame = ttk.Frame(ssl_frame)         btn_frame.grid(row=0, column=1, sticky="n", padx=(10, 0))         ttk.Button(btn_frame, text="OpenSSL Kur (R+D)", command=lambda: self.app.build_manager.build_openssl_async(reinstall=False), width=20).pack(fill=X, pady=2)         ttk.Button(btn_frame, text="OpenSSL Yeniden Kur", command=self.app.build_manager.confirm_reinstall_openssl, width=20, bootstyle=OUTLINE).pack(fill=X, pady=2)         ttk.Button(btn_frame, text="Klasörü Aç", command=lambda: os.startfile(C.OPENSSL_BASE), width=20, bootstyle=SECONDARY).pack(fill=X, pady=2)     def _create_build_panel_content(self, p2: ttk.LabelFrame) -&gt; None:         p2.grid_columnconfigure(0, weight=2)         p2.grid_columnconfigure(1, weight=2)         p2.grid_columnconfigure(2, weight=1)         source_frame = ttk.LabelFrame(p2, text="Kaynak ve Araçlar", padding=10)         source_frame.grid(row=0, column=0, sticky="nsew", padx=2, pady=2)         f_mysql = ttk.Frame(source_frame)         f_mysql.pack(fill=X, pady=2)         ttk.Label(f_mysql, text="MySQL Sürümü:").pack(side=LEFT)         self.cmb_tag = self._register_control(ttk.Combobox(f_mysql, width=20, state="readonly"))         self.cmb_tag.pack(side=LEFT, padx=5, fill=X, expand=True)         ttk.Button(f_mysql, text="Listele", command=self.app.build_manager.fetch_tags_async, width=8, bootstyle=(SECONDARY, OUTLINE)).pack(side=LEFT)         f_tools = ttk.Frame(source_frame)         f_tools.pack(fill=X, pady=2)         ttk.Label(f_tools, text="Araç Modu:").pack(side=LEFT, padx=(0, 10))         self.tool_mode = self._register_control(ttk.StringVar(value=C.ToolMode.BUNDLED.value))         self._register_control(ttk.Radiobutton(f_tools, text=".tools", variable=self.tool_mode, value=C.ToolMode.BUNDLED.value, command=self.app.tool_manager.refresh_tools)).pack(side=LEFT)         self._register_control(ttk.Radiobutton(f_tools, text="Sistem", variable=self.tool_mode, value=C.ToolMode.SYSTEM.value, command=self.app.tool_manager.refresh_tools)).pack(side=LEFT, padx=5)         params_frame = ttk.LabelFrame(p2, text="Parametreler", padding=10)         params_frame.grid(row=0, column=1, sticky="nsew", padx=2, pady=2)         params_frame.grid_columnconfigure(1, weight=1)         ttk.Label(params_frame, text="C++ Sürümü:").grid(row=0, column=0, sticky="w", padx=5, pady=2)         self.cxx_version = self._register_control(ttk.StringVar(value="Latest"))         self._register_control(ttk.Combobox(params_frame, textvariable=self.cxx_version, values=["14", "17", "20", "Preview", "Latest"], width=12, state="readonly")).grid(row=0, column=1, sticky="ew", padx=5)         ttk.Label(params_frame, text="Generator:").grid(row=1, column=0, sticky="w", padx=5, pady=2)         self.generator = self._register_control(ttk.StringVar(value=C.Generator.NINJA.value))         self._register_control(ttk.Combobox(params_frame, textvariable=self.generator, values=[g.value for g in C.Generator], width=12, state="readonly")).grid(row=1, column=1, sticky="ew", padx=5)         ttk.Label(params_frame, text="Runtime:").grid(row=2, column=0, sticky="w", padx=5, pady=2)         self.runtime = self._register_control(ttk.StringVar(value=C.Runtime.MT.value))         self._register_control(ttk.Combobox(params_frame, textvariable=self.runtime, values=[r.value for r in C.Runtime], width=12, state="readonly")).grid(row=2, column=1, sticky="ew", padx=5)         ttk.Label(params_frame, text="Paralel İş:").grid(row=2, column=2, sticky="w", padx=5, pady=2)         self.jobs = self._register_control(ttk.IntVar(value=C.DEFAULT_JOBS))         self._register_control(ttk.Spinbox(params_frame, from_=1, to=128, textvariable=self.jobs, width=6)).grid(row=2, column=3, padx=5)         options_frame = ttk.LabelFrame(p2, text="Seçenekler", padding=10)         options_frame.grid(row=0, column=2, sticky="nsew", padx=2, pady=2)         self.build_release = self._register_control(ttk.BooleanVar(value=True))         self._register_control(ttk.Checkbutton(options_frame, text="Release Derle", variable=self.build_release, bootstyle=PRIMARY)).pack(anchor=W, pady=1)         self.build_debug = self._register_control(ttk.BooleanVar(value=True))         self._register_control(ttk.Checkbutton(options_frame, text="Debug Derle", variable=self.build_debug, bootstyle=PRIMARY)).pack(anchor=W, pady=1)         ttk.Separator(options_frame, orient=HORIZONTAL).pack(fill=X, pady=5)         self.only_client = self._register_control(ttk.BooleanVar(value=True))         self._register_control(ttk.Checkbutton(options_frame, text="Yalnız client kütüphanesi", variable=self.only_client, bootstyle=PRIMARY)).pack(anchor=W, pady=1)         self.prefer_clean = self._register_control(ttk.BooleanVar(value=False))         self._register_control(ttk.Checkbutton(options_frame, text="Temiz başlangıç (.trash'e taşınır)", variable=self.prefer_clean, bootstyle=PRIMARY)).pack(anchor=W, pady=1)         self.auto_cleanup = self._register_control(ttk.BooleanVar(value=False))         self._register_control(ttk.Checkbutton(options_frame, text="'build' klasörlerini temizle", variable=self.auto_cleanup, bootstyle=PRIMARY)).pack(anchor=W, pady=1)         self.btn_info = ttk.Button(options_frame, text="ℹ️ Bilgi", command=self._show_info_modal, bootstyle=(INFO, OUTLINE))         self.btn_info.pack(fill=X, pady=(8, 0))         extra_frame = ttk.Frame(p2)         extra_frame.grid(row=1, column=0, columnspan=3, sticky="ew", padx=2, pady=2)         ttk.Label(extra_frame, text="Ekstra -D Bayrakları:").pack(side=LEFT, padx=(0, 10))         self.extra = self._register_control(ttk.Entry(extra_frame))         self.extra.insert(0, "")         self.extra.pack(side=LEFT, fill=X, expand=True)     def _create_status_panel_content(self, dash: ttk.LabelFrame) -&gt; None:         dash.grid_columnconfigure(0, weight=1)         self.lbl_status_main = ttk.Label(dash, text="Beklemede...", font="-weight bold -size 10")         self.lbl_status_main.grid(row=0, column=0, columnspan=2, sticky="ew", pady=(0, 5))         pb_frame = ttk.Frame(dash)         pb_frame.grid(row=1, column=0, sticky="ew", pady=2)         pb_frame.grid_columnconfigure(1, weight=3)         pb_frame.grid_columnconfigure(3, weight=2)         ttk.Label(pb_frame, text="İndirme:", width=10).grid(row=0, column=0, sticky="e", padx=5)         self.pb_dl = ttk.Progressbar(pb_frame, length=100)         self.pb_dl.grid(row=0, column=1, sticky="ew")         self.lbl_dl_pct = ttk.Label(pb_frame, text="0%", width=5)         self.lbl_dl_pct.grid(row=0, column=2, sticky="w", padx=5)         self.lbl_dl_txt = ttk.Label(pb_frame, text="—", width=30)         self.lbl_dl_txt.grid(row=0, column=3, sticky="ew", padx=5)         ttk.Label(pb_frame, text="Genel İşlem:", width=10).grid(row=1, column=0, sticky="e", padx=5)         self.pb_work = ttk.Progressbar(pb_frame, length=100)         self.pb_work.grid(row=1, column=1, sticky="ew")         self.lbl_work_pct = ttk.Label(pb_frame, text="0%", width=5)         self.lbl_work_pct.grid(row=1, column=2, sticky="w", padx=5)         self.lbl_work_txt = ttk.Label(pb_frame, text="—", width=30)         self.lbl_work_txt.grid(row=1, column=3, sticky="ew", padx=5)         metrics_frame = ttk.Frame(dash)         metrics_frame.grid(row=2, column=0, sticky="ew", pady=5)         ttk.Label(metrics_frame, text="CPU:", font="-weight bold").pack(side=LEFT, padx=(15, 5))         self.lbl_cpu = ttk.Label(metrics_frame, text="0%")         self.lbl_cpu.pack(side=LEFT)         ttk.Label(metrics_frame, text="RAM:", font="-weight bold").pack(side=LEFT, padx=(15, 5))         self.lbl_ram = ttk.Label(metrics_frame, text="0%")         self.lbl_ram.pack(side=LEFT)         ttk.Label(metrics_frame, text="Disk:", font="-weight bold").pack(side=LEFT, padx=(15, 5))         self.lbl_disk = ttk.Label(metrics_frame, text="0 MB/s")         self.lbl_disk.pack(side=LEFT)         ttk.Label(metrics_frame, text=".trash:", font="-weight bold").pack(side=LEFT, padx=(15, 5))         self.lbl_trash_size = ttk.Label(metrics_frame, text="0 B")         self.lbl_trash_size.pack(side=LEFT)         self.btn_empty_trash = ttk.Button(metrics_frame, text="Boşalt", command=self.app.build_manager.empty_trash_folder, bootstyle=(SECONDARY, OUTLINE), width=6)         self.btn_empty_trash.pack(side=LEFT, padx=5)         btns = ttk.Frame(dash)         btns.grid(row=0, column=1, rowspan=3, sticky="ne", padx=(20, 0))         self.btn_start = ttk.Button(btns, text="Başlat", bootstyle=SUCCESS, width=18, command=self.app.build_manager.start_build)         self.btn_start.pack(pady=2, fill=X)         self.btn_stop = ttk.Button(btns, text="Durdur (Acil)", bootstyle=(DANGER, OUTLINE), width=18, command=self.app.task_manager.stop_all, state=DISABLED)         self.btn_stop.pack(pady=2, fill=X)         self.btn_clear_cache = ttk.Button(btns, text="CMake Önbelleğini Temizle", bootstyle=(WARNING, OUTLINE), width=18, command=self.app.build_manager.clear_cmake_cache)         self.btn_clear_cache.pack(pady=2, fill=X)         self.btn_open_install = ttk.Button(btns, text="Install Klasörünü Aç", bootstyle=(INFO, OUTLINE), width=18, command=self._open_install_folder)         self.btn_open_install.pack(pady=2, fill=X)              def _create_log_widgets(self, parent: ttk.LabelFrame) -&gt; None:         parent.rowconfigure(1, weight=1)         parent.columnconfigure(0, weight=1)         filter_frame = ttk.Frame(parent, padding=(0, 5))         filter_frame.grid(row=0, column=0, sticky="ew")         ttk.Label(filter_frame, text="Filtrele:").pack(side=LEFT, padx=(0, 5))         for level in C.LogLevel:             var = tk.BooleanVar(value=True)             chk = ttk.Checkbutton(filter_frame, text=level.name.capitalize(), variable=var,                                   command=lambda l=level.value, v=var: self.toggle_log_filter(l, v.get()),                                   bootstyle="outline-toolbutton")             chk.pack(side=LEFT, padx=2)             self.log_filter_vars[level.value] = var         log_text_frame = ttk.Frame(parent)         log_text_frame.grid(row=1, column=0, sticky="nsew")         log_text_frame.rowconfigure(0, weight=1)         log_text_frame.columnconfigure(0, weight=1)         self.log = scrolledtext.ScrolledText(log_text_frame, font=("Consolas", 10), wrap=WORD, undo=True)         self.log.grid(row=0, column=0, sticky="nsew")         self.log_context_menu = tk.Menu(self.log, tearoff=0)         self.log_context_menu.add_command(label="Kopyala", command=self.copy_log_selection)         self.log_context_menu.add_command(label="Tümünü Kopyala", command=self.copy_all_logs)         self.log_context_menu.add_separator()         self.log_context_menu.add_command(label="Tümünü Temizle", command=self._clear_log)         self.log.bind("&lt;Button-3&gt;", self.show_log_context_menu)         colors = {             C.LogLevel.STAGE: ("#198754", None), C.LogLevel.CMD: ("#0d6efd", None),             C.LogLevel.WARN: ("#664d03", "#fff3cd"), C.LogLevel.ERR: ("#842029", "#f8d7da"),             C.LogLevel.INFO: ("#6c757d", None)         }         for level, (fg, bg) in colors.items():             if bg:                 self.log.tag_config(level.value, foreground=fg, background=bg)             else:                 self.log.tag_config(level.value, foreground=fg)     def _create_task_panel(self, parent: ttk.LabelFrame) -&gt; None:         parent.rowconfigure(0, weight=1)         parent.columnconfigure(0, weight=1)         self.task_tree = ttk.Treeview(parent, columns=("name", "status", "progress", "runtime"), show="headings", height=4)         for c, t, w in (("name","Görev",200), ("status","Durum",100), ("progress","İlerleme",120), ("runtime","Süre",100)):             self.task_tree.heading(c, text=t)             self.task_tree.column(c, width=w, anchor=W)         self.task_tree.grid(row=0, column=0, sticky="nsew")         btn_frame = ttk.Frame(parent)         btn_frame.grid(row=1, column=0, sticky="ew", pady=(5,0))         ttk.Button(btn_frame, text="Seçili Görevi Durdur", command=self.app.task_manager.terminate_selected_task, bootstyle=(WARNING, OUTLINE)).pack(fill=X)     def pump_queue(self) -&gt; None:         log_batch = []         try:             while not self.app.q.empty():                 log_batch.append(self.app.q.get_nowait())             if log_batch:                 self._process_log_batch(log_batch)         except queue.Empty:             pass         finally:             self.app.after(100, self.pump_queue)     def _process_log_batch(self, batch: list[tuple]) -&gt; None:         full_text_to_insert = ""         is_scrolled_to_bottom = self.log.yview()[1] &gt;= 1.0         for kind, *rest in batch:             if kind == "log":                 timestamp = datetime.datetime.now().strftime('%H:%M:%S')                 message, level = cast(str, rest[0]), cast(str, rest[1])                 for ln in message.splitlines():                     start_index = self.log.index(f"end-1c")                     line_content = f"[{timestamp}] {ln}\n"                     full_text_to_insert += line_content                     end_index = f"{start_index}+{len(line_content)}c"                     self.log.tag_add(level, start_index, end_index)             elif kind == "status":                 self.lbl_status_main.config(text=rest[0])             elif kind == "dl":                 val, txt = int(rest[0]), str(rest[1])                 self.pb_dl['value'] = val                 self.lbl_dl_pct.config(text=f"{val}%")                 self.lbl_dl_txt.config(text=txt)             elif kind == "work":                 val, txt = int(rest[0]), str(rest[1])                 self.pb_work['value'] = val                 self.lbl_work_pct.config(text=f"{val}%")                 self.lbl_work_txt.config(text=txt)             elif kind == "task_progress":                 task_id, progress_text = cast(str, rest[0]), cast(str, rest[1])                 if task_id in self.app.task_manager.task_registry:                     self.app.task_manager.task_registry[task_id]['progress'] = progress_text                  if full_text_to_insert:             self.log.insert(END, full_text_to_insert)                          num_lines = int(self.log.index('end-1c').split('.')[0])             if num_lines &gt; C.MAX_LOG_LINES:                 self.log.delete("1.0", f"{num_lines - C.MAX_LOG_LINES}.0")                          if is_scrolled_to_bottom:                 self.log.see(END)     def set_controls_state(self, state: str) -&gt; None:         self.btn_start.config(state=state)         self.btn_stop.config(state='normal' if state == 'disabled' else 'disabled')         self.btn_clear_cache.config(state=state)         self.btn_open_install.config(state=state)         for widget in self.control_widgets:             try:                 if isinstance(widget, ttk.Variable):                     continue                 widget.config(state="readonly" if isinstance(widget, ttk.Combobox) and state == 'normal' else state)             except tk.TclError:                 pass         for tree in (self.tree, self.ssl_tree):             tree.config(selectmode='browse' if state == 'normal' else 'none')              def update_build_stats(self, last_line: str, task_id: str) -&gt; None:         m = C._RE_NINJA.search(last_line)         if m:             d, t = int(m.group(1)), int(m.group(2))             pct = int(100.0 * d / max(1, t))             progress_text = f"[{d}/{t}] {pct}%"             self.bus.update_task_progress(task_id, progress_text)             self.bus.progress("work", pct, progress_text)         else:             self.bus.log(last_line, C.LogLevel.CMD)     def update_resource_monitor(self) -&gt; None:         try:             self.lbl_cpu.config(text=f"{psutil.cpu_percent(interval=None):.1f}%")             self.lbl_ram.config(text=f"{psutil.virtual_memory().percent:.1f}%")             current_time, current_io = time.time(), psutil.disk_io_counters()             delta_time = current_time - self.last_update_time             if delta_time &gt; 0:                 read_bytes = current_io.read_bytes - self.last_disk_io.read_bytes                 write_bytes = current_io.write_bytes - self.last_disk_io.write_bytes                 total_mb_s = (read_bytes + write_bytes) / (1024 * 1024) / delta_time                 self.lbl_disk.config(text=f"{total_mb_s:.1f} MB/s")             self.last_update_time, self.last_disk_io = current_time, current_io         except (psutil.Error, ZeroDivisionError):             for label in (self.lbl_cpu, self.lbl_ram, self.lbl_disk):                 label.config(text="N/A")         finally:             self.app.after(1000, self.update_resource_monitor)          def update_trash_monitor(self) -&gt; None:         try:             if C.TRASH.exists():                 total_size = sum(f.stat().st_size for f in C.TRASH.glob('**/*') if f.is_file())                 if total_size &lt; 1024:                     size_str = f"{total_size} B"                 elif total_size &lt; 1024**2:                     size_str = f"{total_size/1024:.1f} KB"                 elif total_size &lt; 1024**3:                     size_str = f"{total_size/1024**2:.1f} MB"                 else:                     size_str = f"{total_size/1024**3:.1f} GB"                 self.lbl_trash_size.config(text=size_str)             else:                 self.lbl_trash_size.config(text="0 B")         except Exception:             self.lbl_trash_size.config(text="N/A")         finally:             self.app.after(5000, self.update_trash_monitor)                  def load_settings(self) -&gt; None:         try:             if C.CONFIG_FILE.exists():                 with open(C.CONFIG_FILE, 'r', encoding='utf-8') as f:                     settings = json.load(f)                 self.cxx_version.set(settings.get("cxx_version", "Latest"))                 self.generator.set(settings.get("generator", C.Generator.NINJA.value))                 self.runtime.set(settings.get("runtime", C.Runtime.MT.value))                 self.jobs.set(settings.get("jobs", C.DEFAULT_JOBS))                 self.tool_mode.set(settings.get("tool_mode", C.ToolMode.BUNDLED.value))                 self.last_tag_from_config = settings.get("last_tag")                 for var, key in ((self.build_release,"build_release"),(self.build_debug,"build_debug"),(self.only_client,"only_client"),                                  (self.prefer_clean,"prefer_clean"),(self.auto_cleanup,"auto_cleanup")):                     var.set(settings.get(key, var.get()))                 self.extra.delete(0, END)                 self.extra.insert(0, settings.get("extra_defs", ""))                 self.bus.log("Ayarlar 'config.json' dosyasından yüklendi.", C.LogLevel.INFO)         except (json.JSONDecodeError, TypeError, KeyError) as e:             self.bus.log(f"'config.json' dosyası okunurken hata: {e}", C.LogLevel.WARN)     def save_settings(self) -&gt; None:         settings = {"cxx_version": self.cxx_version.get(), "generator": self.generator.get(), "runtime": self.runtime.get(), "jobs": self.jobs.get(),                     "tool_mode": self.tool_mode.get(), "build_release": self.build_release.get(), "build_debug": self.build_debug.get(),                     "only_client": self.only_client.get(), "prefer_clean": self.prefer_clean.get(), "auto_cleanup": self.auto_cleanup.get(),                     "extra_defs": self.extra.get(), "last_tag": self.cmb_tag.get()}         try:             with open(C.CONFIG_FILE, 'w', encoding='utf-8') as f:                 json.dump(settings, f, indent=4)         except OSError as e:             self.bus.log(f"Ayarlar kaydedilemedi: {e}", C.LogLevel.ERR)     def _clear_log(self) -&gt; None:         self.log.delete("1.0", END)     def _save_log(self) -&gt; None:         log_content = self.log.get("1.0", END)         if not log_content.strip():             messagebox.showinfo("Bilgi", "Kaydedilecek log içeriği bulunmuyor.")             return         filename = filedialog.asksaveasfilename(title="Log Dosyasını Kaydet", initialfile=f"mysql_builder_pro_log_{datetime.datetime.now():%Y%m%d_%H%M%S}.txt",                                                 defaultextension=".txt", filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")])         if filename:             try:                 with open(filename, 'w', encoding='utf-8') as f:                     f.write(log_content)                 self.bus.log(f"Loglar başarıyla '{filename}' dosyasına kaydedildi.", C.LogLevel.INFO)             except OSError as e:                 self.bus.log(f"Log dosyası kaydedilemedi: {e}", C.LogLevel.ERR)                 messagebox.showerror("Hata", f"Dosya kaydedilemedi:\n{e}")     def _open_install_folder(self) -&gt; None:         C.INST.mkdir(parents=True, exist_ok=True)         try:             os.startfile(C.INST)             self.bus.log(f"'{C.INST}' klasörü açıldı.", C.LogLevel.INFO)         except Exception as e:             self.bus.log(f"Klasör açılamadı: {e}", C.LogLevel.ERR)             messagebox.showerror("Hata", f"'{C.INST}' klasörü açılamadı:\n{e}")     def _show_info_modal(self) -&gt; None:         info_window = ttk.Toplevel(title="Uygulama Kullanım Rehberi")         info_window.geometry("850x750")         info_window.transient(self.root)         info_window.grab_set()         text_area = scrolledtext.ScrolledText(info_window, wrap=WORD, font=("Segoe UI", 10), relief="flat", padx=10, pady=10)         text_area.pack(fill=BOTH, expand=True)         info_text = """MySQL Builder Pro Kullanım Rehberi (v1.0.0) ========================================== Bu araç, MySQL kaynak kodunu (özellikle C++ connector/client kütüphanesini) Windows ortamında derleme sürecini otomatikleştirmek için tasarlanmıştır. Normalde karmaşık ve çok adımlı olan bu işlemi, kullanıcı dostu bir arayüz üzerinden yönetmenizi sağlar. --- ÖN GEREKSİNİMLER --- - **Windows 10/11 (64-bit)** - **Visual Studio 2022:** "Masaüstü geliştirme (C++)" (Desktop development with C++) iş yükünün kurulu olması ZORUNLUDUR. Bu, derleme için gerekli olan C++ derleyicisini (cl.exe), linker'ı ve Windows SDK'larını içerir. --- ADIM ADIM KULLANIM --- **Adım 1: Gerekli Araçların Kurulumu** Uygulama, derleme için Git, CMake, Ninja, Perl gibi bir dizi araca ihtiyaç duyar. Bu araçları sizin için otomatik olarak indirip kurabilir. 1.  Uygulamayı ilk açtığınızda **"Araç Durumu"** paneline bakın. 2.  Eğer listede "❌ Yok" olarak işaretlenmiş araçlar varsa, sağ taraftaki **"Eksikleri Kur"** butonuna tıklayın. 3.  Uygulama, gerekli tüm araçları programın yanındaki `.tools` klasörüne indirecek ve kuracaktır. İşlem tamamlandığında bir bildirim alacaksınız. 4.  Eğer mevcut kurulumda bir sorun olduğunu düşünüyorsanız, **"Hepsini Yeniden Kur"** butonu ile tüm araçları silip baştan kurabilirsiniz. **Adım 2: OpenSSL Kütüphanesinin Derlenmesi** MySQL, güvenlik katmanı için OpenSSL kütüphanesine ihtiyaç duyar. Bu araç, OpenSSL'i de sizin için kaynak kodundan derler. 1.  **"OpenSSL Durumu"** paneline bakın. Başlangıçta tüm konfigürasyonlar "❌ Eksik" görünecektir. 2.  Sağ taraftaki **"OpenSSL Kur (R+D)"** butonuna tıklayın.     - **(R+D)**, hem **Release** hem de **Debug** konfigürasyonlarının derleneceği anlamına gelir. Bu, her iki modda da MySQL derlemesi yapabilmeniz için gereklidir. 3.  Bu işlem biraz zaman alabilir. İşlem tamamlandığında durum tablosu güncellenecek ve "✔️ OK" olarak görünecektir. **Adım 3: Derleme Ayarlarını Yapılandırma** Tüm araçlar ve bağımlılıklar hazır olduğunda, neyi ve nasıl derlemek istediğinizi seçebilirsiniz. -   **MySQL Sürümü:** Derlemek istediğiniz MySQL versiyonunu seçin. **"Listele"** butonu en güncel sürümleri GitHub'dan çeker. -   **Araç Modu:** Genellikle **".tools"** seçeneğinde kalmalıdır. Bu, uygulamanın kendi kurduğu araçları kullanmasını sağlar. -   **Parametreler:**     -   **C++ Sürümü:** Genellikle "Latest" olarak bırakılabilir.     -   **Generator:** "Ninja" genellikle daha hızlıdır ve varsayılan olarak seçilidir.     -   **Runtime:**         -   **MT:** Statik Runtime. Derlenen kütüphane, C++ runtime'ını kendi içinde barındırır. Dağıtımı daha kolaydır.         -   **MD:** Dinamik Runtime. Derlenen kütüphane, sistemde Visual C++ Redistributable paketinin kurulu olmasını gerektirir. -   **Seçenekler:**     -   **Release/Debug Derle:** Hangi konfigürasyonları derlemek istediğinizi seçin. Genellikle ikisi de seçilir.     -   **Yalnız client kütüphanesi:** Bu seçenek, tüm MySQL sunucusunu değil, sadece C++ uygulamalarınıza bağlayacağınız `mysqlclient.lib` ve ilgili başlık dosyalarını derler. **Çoğu kullanım durumu için bu seçenek işaretli kalmalıdır.**     -   **Temiz başlangıç:** Derlemeye başlamadan önce `src`, `build`, `install` klasörlerini `.trash` klasörüne taşıyarak temiz bir başlangıç yapar.     -   **'build' klasörlerini temizle:** Her bir derleme bittikten sonra, diskte yer kaplayan geçici derleme klasörünü otomatik olarak siler. **Adım 4: Derlemeyi Başlatma** 1.  Tüm ayarları yaptıktan sonra sağ alttaki yeşil **"Başlat"** butonuna tıklayın. 2.  Uygulama, seçtiğiniz MySQL sürümünün kaynak kodunu indirecek, yapılandıracak ve derleyecektir. 3.  İlerleme çubuklarından ve log penceresinden süreci takip edebilirsiniz. 4.  Derleme sırasında bir sorun olursa, kırmızı **"Durdur (Acil)"** butonu ile işlemi sonlandırabilirsiniz. **Adım 5: Sonuçları Kullanma** 1.  Derleme başarıyla tamamlandığında bir bildirim alacaksınız. 2.  Tüm nihai dosyalar (`.lib`, `.dll`, `.h` dosyaları) programın yanındaki **`install`** klasörünün içine, seçtiğiniz Runtime ve Sürüm'e göre alt klasörlere yerleştirilir. 3.  **"Install Klasörünü Aç"** butonuna tıklayarak bu klasöre kolayca erişebilirsiniz. """         text_area.insert(END, info_text.strip())         text_area.config(state=DISABLED)         ttk.Button(info_window, text="Kapat", command=info_window.destroy, bootstyle=SECONDARY).pack(pady=10)              def toggle_log_filter(self, tag_name: str, is_visible: bool) -&gt; None:         self.log.tag_config(tag_name, elide=not is_visible)     def show_log_context_menu(self, event: tk.Event) -&gt; None:         if self.log.tag_ranges("sel"):             self.log_context_menu.entryconfig("Kopyala", state="normal")         else:             self.log_context_menu.entryconfig("Kopyala", state="disabled")         self.log_context_menu.tk_popup(event.x_root, event.y_root)     def copy_log_selection(self) -&gt; None:         try:             selected_text = self.log.get(tk.SEL_FIRST, tk.SEL_LAST)             self.root.clipboard_clear()             self.root.clipboard_append(selected_text)             self.bus.log("Seçili loglar panoya kopyalandı.", C.LogLevel.INFO)         except tk.TclError:             pass     def copy_all_logs(self) -&gt; None:         all_text = self.log.get("1.0", tk.END)         self.root.clipboard_clear()         self.root.clipboard_append(all_text)         self.bus.log("Tüm loglar panoya kopyalandı.", C.LogLevel.INFO) class ToolManager:     def __init__(self, app: 'App'):         self.app = app         self.bus = app.bus         self.TOOLS_META = [             {"key":"7zip","name":"7-Zip","exe":"7z","altexe":["7za","7zr"],"ensure":self._ensure_7zip,"vercmd":["7z"]},             {"key":"git","name":"Git (MinGit)","exe":"git","ensure":lambda bus, se: self._ensure_zip_tool("mingit",C.URLS["mingit"],"mingit",bus, se),"vercmd":["git","--version"]},             {"key":"cmake","name":"CMake","exe":"cmake","ensure":lambda bus, se: self._ensure_zip_tool("cmake",C.URLS["cmake"],"cmake",bus, se),"vercmd":["cmake","--version"]},             {"key":"ninja","name":"Ninja","exe":"ninja","ensure":lambda bus, se: self._ensure_zip_tool("ninja",C.URLS["ninja"],None,bus, se),"vercmd":["ninja","--version"]},             {"key":"perl","name":"Strawberry Perl","exe":"perl","ensure":lambda bus, se: self._ensure_zip_tool("perl",C.URLS["perl"],"perl",bus, se),"vercmd":["perl","-e","print $^V"]},             {"key":"nasm","name":"NASM","exe":"nasm","ensure":lambda bus, se: self._ensure_zip_tool("nasm",C.URLS["nasm"],"nasm",bus, se),"vercmd":["nasm","-v"]},             {"key":"sccache","name":"sccache (Önerilir)","exe":"sccache","ensure":self._ensure_sccache,"vercmd":["sccache","--version"]},         ]              def vs_env_paths(self) -&gt; str | None:         vswhere = Path(r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe")         candidates = []         if vswhere.exists():             cmd_str = subprocess.list2cmdline([str(vswhere), "-latest", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-property", "installationPath"])             base_path = run_out(cmd_str).splitlines()             base = Path((base_path[0] if base_path else "").strip())             if base.is_dir():                 candidates += [base / "VC/Auxiliary/Build/vcvars64.bat", base / "Common7/Tools/VsDevCmd.bat"]         root = Path(r"C:\Program Files\Microsoft Visual Studio\2022")         for ed in ("Community", "Professional", "Enterprise", "BuildTools"):             base = root / ed             candidates += [base / "VC/Auxiliary/Build/vcvars64.bat", base / "Common7/Tools/VsDevCmd.bat"]         return next((str(p) for p in candidates if p.exists()), None)     def which(self, name: str) -&gt; str | None:         return shutil.which(name)     def which_only_bundled(self, name: str) -&gt; str | None:         for folder in C.BUNDLED_PATHS:             for ext in ["", ".exe", ".bat", ".cmd"]:                 p = folder / (name + ext)                 if p.exists() and p.is_file():                     return str(p)         return None     def detect_tool(self, meta: dict, mode: C.ToolMode) -&gt; tuple[str | None, str | None, str | None]:         executables = [meta['exe']] + meta.get('altexe', [])         p, src = (None, None)                  if mode == C.ToolMode.BUNDLED:             p = next((path for exe in executables if (path := self.which_only_bundled(exe))), None)             if p:                 src = "Bundled"         else:             p = next((path for exe in executables if (path := self.which(exe))), None)             if p:                 src = "System"         if not p:             return None, None, None                      exe_path = Path(p)         try:             is_bundled = exe_path.resolve().is_relative_to(C.TOOLS.resolve())             if mode == C.ToolMode.BUNDLED and not is_bundled:                 src = "System"             if mode == C.ToolMode.SYSTEM and is_bundled:                 src = "Bundled"         except (ValueError, OSError):             pass         vercmd = meta.get("vercmd", [])[:]         if vercmd:             vercmd[0] = str(exe_path)         else:             vercmd = [str(exe_path), "--version"]                  env_paths = C.BUNDLED_PATHS if src == "Bundled" else []         with temp_env_path(env_paths):             out = run_out(vercmd, cwd=exe_path.parent)         if out.startswith("ERR:"):             return str(exe_path), "Hata", src                  ver_patterns = [r"v?(\d+\.\d+(?:\.\d+)?(?:\.\d+)?)", r"version[\s:]*(\d+\.\d+(?:\.\d+)?)"]         ver = next((m.group(1) for pat in ver_patterns if (m := re.search(pat, out, re.I))), out.splitlines()[0][:40] if out else "")         return str(exe_path), ver, src     def refresh_tools(self) -&gt; None:         ui = self.app.ui_manager         ui.tree.delete(*ui.tree.get_children())         mode = C.ToolMode(ui.tool_mode.get())         for meta in self.TOOLS_META:             path, ver, src = self.detect_tool(meta, mode)             tags = () if path else ("missing",)             ui.tree.insert("", END, iid=meta["key"], values=(meta["name"], "✔️ Yüklü" if path else "❌ Yok", ver or "-", src or "-", path or "-"), tags=tags)         ui.tree.tag_configure("missing", background="#f8d7da", foreground="#842029")     def confirm_reinstall_all(self) -&gt; None:         if messagebox.askyesno("Onay", "Tüm araçlar .tools klasöründen silinip yeniden indirilecektir. Bu işlem zaman alabilir.\n\nDevam etmek istiyor musunuz?"):             self.install_tools_async(reinstall=True)     def install_tools_async(self, missing: bool = False, reinstall: bool = False) -&gt; None:         if self.app._is_busy:             return         self.app.set_busy(True)         stop_event = threading.Event()         self.app.task_manager.register(name="Araç Kurulum Yöneticisi", target=self._install_tools_worker,                                        args=(missing, reinstall, stop_event), stop_event=stop_event, is_build_task=False)     def _install_tools_worker(self, missing: bool, reinstall: bool, stop_event: threading.Event, task_id: str) -&gt; None:         try:             metas_to_install = self.TOOLS_META             if missing:                 metas_to_install = [m for m in self.TOOLS_META if not self.detect_tool(m, C.ToolMode.BUNDLED)[0]]                 if not metas_to_install:                     self.bus.log("Tüm araçlar zaten .tools klasöründe yüklü.", C.LogLevel.INFO)                     messagebox.showinfo("Araçlar", "Eksik araç bulunamadı.")                     self.app.task_manager.task_registry[task_id]['success'] = True                     self.app.set_busy(False)                     return             with ThreadPoolExecutor() as executor:                 futures = [executor.submit(self._install_tool, mt, reinstall, stop_event) for mt in metas_to_install]                 for future in futures:                     future.result()             if not stop_event.is_set():                 messagebox.showinfo("Araçlar", "Seçilen araçların kurulumu tamamlandı.")             self.app.task_manager.task_registry[task_id]['success'] = True         except Exception as e:             if not stop_event.is_set():                 self.bus.log(f"Araç kurulum hatası: {e}\n{traceback.format_exc()}", C.LogLevel.ERR)                 messagebox.showerror("Hata", f"Araç kurulumu başarısız oldu: {e}")             self.app.task_manager.task_registry[task_id]['success'] = False         finally:             self.app.after(0, self.refresh_tools)             self.app.set_busy(False)     def _install_tool(self, meta: dict, reinstall: bool, stop_event: threading.Event) -&gt; None:         if stop_event.is_set():             return         self.bus.log(f"{meta['name']} kurulumu başlıyor...", C.LogLevel.STAGE)                  if reinstall:             if meta['key'] == '7zip' and (C.TOOLS / "7zip").exists():                 shutil.rmtree(C.TOOLS / "7zip", ignore_errors=True)             elif meta.get('subfolder') and (C.TOOLS / meta['subfolder']).exists():                 shutil.rmtree(C.TOOLS / meta['subfolder'], ignore_errors=True)             elif meta['key'] == 'sccache' and (C.TOOLS / 'sccache.exe').exists():                 (C.TOOLS / 'sccache.exe').unlink(missing_ok=True)                  meta["ensure"](self.bus, stop_event)              def _http_get(self, url: str, dest: Path, bus: Bus) -&gt; bool:         bus.log(f"[DL] Başlıyor: {url} → {dest}", C.LogLevel.STAGE)         dest.parent.mkdir(parents=True, exist_ok=True)         try:             with requests.get(url, stream=True, timeout=60) as r:                 r.raise_for_status()                 total_size = int(r.headers.get("Content-Length", 0))                 done = 0                 last_pc = -1                 with open(dest, "wb") as f:                     for chunk in r.iter_content(256 * 1024):                         if not chunk:                             continue                         f.write(chunk)                         done += len(chunk)                         if total_size:                             pc = int(done * 100 / total_size)                             if pc != last_pc:                                 bus.progress("dl", pc, f"{done/1e6:.1f}/{total_size/1e6:.1f} MB")                                 last_pc = pc             bus.progress("dl", 100, "OK")             bus.log("[DL] Tamamlandı", C.LogLevel.STAGE)             return True         except requests.RequestException as e:             bus.log(f"[DL] Hata: {e}", C.LogLevel.ERR)             dest.unlink(missing_ok=True)             return False     def _seven_extract(self, arc: Path, out: Path, bus: Bus) -&gt; bool:         seven = next((p for n in ("7z", "7zr") for p in (self.which_only_bundled(n), self.which(n)) if p), None)         if not seven:             self._ensure_7zip(bus, None)             seven = next((p for n in ("7z", "7zr") for p in (self.which_only_bundled(n), self.which(n)) if p), None)         if not seven:             return False         parser = SevenZipProgressParser(bus)         lines = []         cmd = f'"{seven}" x "{arc}" -o"{out}" -y -bsp1 -bso1 -bse1'         ok = stream_proc(cmd, os.environ.copy(), on_line=lambda line: (lines.append(line), parser.parse_line(line)))         output_text = "\n".join(lines)         if "Everything is Ok".lower() in output_text.lower():             return True         return ok and out.exists() and any(out.rglob("*"))     def _python_unzip(self, z: Path, out: Path, bus: Bus) -&gt; bool:         with zipfile.ZipFile(z) as zp:             infolist = zp.infolist()             n = len(infolist)             last_pc = -1             for i, info in enumerate(infolist, 1):                 zp.extract(info, out)                 pc = int(i * 100 / n) if n else 0                 if pc != last_pc:                     bus.progress("work", pc, f"{i}/{n}")                     last_pc = pc         bus.progress("work", 100, "Tamamlandı")         return True     def _ensure_zip_tool(self, name: str, url: str, subfolder: str | None, bus: Bus, stop_event: threading.Event | None):         if stop_event and stop_event.is_set():             return         zip_path = C.DL / f"{name}.zip"         if not zip_path.exists():             bus.progress("dl", 0, f"{name} indiriliyor")             if not self._http_get(url, zip_path, bus):                 raise RuntimeError(f"{name} indirilemedi")         out = C.TOOLS / subfolder if subfolder else C.TOOLS         out.mkdir(parents=True, exist_ok=True)         bus.status(f"'{name}' arşivi çıkarılıyor...")         bus.progress("work", 0, "Çıkarma başlıyor...")         if not (self._seven_extract(zip_path, out, bus) or self._python_unzip(zip_path, out, bus)):             raise RuntimeError(f"{name} arşivi açılamadı")         bus.progress("work", 100, f"{name} hazır")         if subfolder:             kids = list(out.glob("*"))             if len(kids) == 1 and kids[0].is_dir():                 tmp = out.parent / f"_{subfolder}"                 shutil.move(str(kids[0]), str(tmp))                 shutil.rmtree(out, ignore_errors=True)                 tmp.rename(out)     def _ensure_7zip(self, bus: Bus, stop_event: threading.Event | None) -&gt; None:         zdir = C.TOOLS / "7zip"         zdir.mkdir(exist_ok=True)         if not (zdir / "7z.exe").exists():             zr, ze = zdir / "7zr.exe", C.DL / "7zextra.7z"             if not zr.exists():                 self._http_get(C.URLS["7zr"], zr, bus)             if not ze.exists():                 self._http_get(C.URLS["7zextra"], ze, bus)             subprocess.call(f'"{zr}" x "{ze}" -o"{zdir}" -y', shell=True, creationflags=subprocess.CREATE_NO_WINDOW)     def _ensure_sccache(self, bus: Bus, stop_event: threading.Event | None) -&gt; None:         archive = C.DL / "sccache.tar.gz"         if not archive.exists():             self._http_get(C.URLS["sccache"], archive, bus)         bus.status("sccache arşivi çıkarılıyor...")         with tarfile.open(archive, "r:gz") as tar:             exe_member = next((m for m in tar.getmembers() if "sccache.exe" in m.name), None)             if not exe_member:                 raise RuntimeError("sccache.exe arşivde bulunamadı.")             exe_member.name = Path(exe_member.name).name             tar.extract(exe_member, path=C.TOOLS)         if not (C.TOOLS / "sccache.exe").exists():             raise RuntimeError("sccache.exe .tools'a taşınamadı.")         bus.log("✔️ sccache başarıyla kuruldu.", C.LogLevel.STAGE) class BuildManager:     def __init__(self, app: 'App'):         self.app = app         self.bus = app.bus         self.active_pids = set()         self.pids_lock = threading.Lock()          def add_pid(self, pid: int) -&gt; None:         with self.pids_lock:             self.active_pids.add(pid)     def remove_pid(self, pid: int) -&gt; None:         with self.pids_lock:             self.active_pids.discard(pid)     def get_active_pids(self) -&gt; set[int]:         with self.pids_lock:             return self.active_pids.copy()     def clear_active_pids(self) -&gt; None:         with self.pids_lock:             self.active_pids.clear()     def start_build(self) -&gt; None:         ui = self.app.ui_manager         if self.app._is_busy:             self.bus.log("Zaten devam eden bir derleme var.", C.LogLevel.WARN)             return         ui.log.delete("1.0", END)         if not self._run_preflight_checks():             self.bus.log("Ön kontrol başarısız, derleme iptal.", C.LogLevel.ERR)             return         if not ui.build_release.get() and not ui.build_debug.get():             messagebox.showwarning("Seçim Gerekli", "En az bir derleme konfigürasyonu seçin.")             return         ui.pb_dl['value'] = 0         ui.pb_work['value'] = 0         ui.lbl_dl_pct.config(text="0%")         ui.lbl_work_pct.config(text="0%")         self.app.set_busy(True)         try:             tag = ui.cmb_tag.get().strip()             shared_config = {"tag": tag, "tool_mode": C.ToolMode(ui.tool_mode.get()), "generator": C.Generator(ui.generator.get()),                              "runtime": C.Runtime(ui.runtime.get()), "cxx_version": ui.cxx_version.get(), "jobs": ui.jobs.get(),                              "extra_defs": ui.extra.get(), "only_client": ui.only_client.get(), "source_dir": C.SRC / tag}         except Exception as e:             self.bus.log(f"Yapılandırma okunurken hata: {e}", C.LogLevel.ERR)             self.app.set_busy(False)             return         stop_event = threading.Event()         self.app.task_manager.register(name="Ana Derleme Yöneticisi", target=self._start_build_manager, args=(shared_config, stop_event), stop_event=stop_event, is_build_task=False)     def _start_build_manager(self, shared_config: dict, stop_event: threading.Event, task_id: str) -&gt; None:         ui = self.app.ui_manager         try:             configs = []             if ui.build_release.get():                 configs.append(BuildConfig(build_type=C.BuildType.RELEASE, **shared_config))             if ui.build_debug.get():                 configs.append(BuildConfig(build_type=C.BuildType.DEBUG, **shared_config))             if not configs:                 raise RuntimeError("Derlenecek yapılandırma bulunamadı.")                          self.bus.update_task_progress(task_id, "Temizlik...")             if ui.prefer_clean.get():                 self._do_clean_start()             if stop_event.is_set(): return             self.bus.update_task_progress(task_id, "OpenSSL Derleniyor...")             self._build_openssl_sequence(C.Runtime(shared_config['runtime']), stop_event)             if stop_event.is_set(): return             self.bus.update_task_progress(task_id, "MySQL Kaynakları Getiriliyor...")             src_path = self._fetch_source(shared_config['tag'], stop_event)             if not src_path:                 raise RuntimeError("MySQL kaynakları alınamadı.")             for bc in configs:                 bc.source_dir = src_path             if stop_event.is_set(): return             all_success = True             for config in configs:                 self.bus.update_task_progress(task_id, f"{config.build_type.value} derleniyor...")                 sub_task_id = f"build_{config.build_type.value.lower()}_{uuid.uuid4().hex[:6]}"                 worker_thread = self.app.task_manager.register(name=f"MySQL Derleme: {config.build_type.value}", target=self._build_worker, args=(config, stop_event), stop_event=stop_event, task_id=sub_task_id)                 worker_thread.join()                 if stop_event.is_set():                     return                 if not self.app.task_manager.task_registry.get(sub_task_id, {}).get('success', False):                     all_success = False                     self.bus.log(f"{config.build_type.value} derlemesi başarısız, süreç durduruldu.", C.LogLevel.ERR)                     break             self._finalize_build_process(all_success)         except Exception as e:             if not stop_event.is_set():                 self.bus.log(f"ANA YÖNETİCİDE KRİTİK HATA: {e}\n{traceback.format_exc()}", C.LogLevel.ERR)                 self.bus.status(f"Kritik Hata: {e}")                 messagebox.showerror("Kritik Hata", f"Beklenmedik hata:\n\n{e}")         finally:             self.app.task_manager.task_registry[task_id]['success'] = True             self.app.set_busy(False)     def _finalize_build_process(self, all_success: bool) -&gt; None:         if all_success:             self.bus.status("Tüm işlemler başarıyla tamamlandı!")             messagebox.showinfo("MySQL Builder", "Tüm derleme işlemleri başarıyla tamamlandı.")             self.app.q.put(("auto_open_folder", str(C.INST)))         else:             self.bus.status("Derleme bir veya daha fazla hatayla tamamlandı.")             messagebox.showwarning("Derleme Başarısız", "Bir veya daha fazla derleme görevi başarısız oldu. Detaylar için logları inceleyin.")     def _build_worker(self, config: BuildConfig, stop_event: threading.Event, task_id: str) -&gt; None:         ui = self.app.ui_manager         try:             config.build_dir.mkdir(parents=True, exist_ok=True)             config.install_dir.mkdir(parents=True, exist_ok=True)             if not self._configure_mysql(config, stop_event=stop_event):                 raise RuntimeError("CMake yapılandırması başarısız.")             if stop_event.is_set():                 return             ok_build, _, _ = self._build_mysql(config, task_id, stop_event=stop_event)             if not ok_build:                 raise RuntimeError("MySQL derleme adımı başarısız.")             if ui.auto_cleanup.get():                 self.bus.log(f"[{config.build_type.value}] 'build' klasörü temizleniyor: {config.build_dir}", C.LogLevel.INFO)                 shutil.rmtree(config.build_dir, ignore_errors=True)             self.app.task_manager.task_registry[task_id]['success'] = True         except Exception as e:             self.app.task_manager.task_registry[task_id]['success'] = False             self.app.task_manager.task_registry[task_id]['error'] = str(e)             if not stop_event.is_set():                 self.bus.log(f"[{config.build_type.value}] KRİTİK HATA: {e}\n{traceback.format_exc()}", C.LogLevel.ERR)     def _run_preflight_checks(self) -&gt; bool:         ui = self.app.ui_manager         tool_manager = self.app.tool_manager                  self.bus.log("--- Uçuş Öncesi Kontroller Başlatıldı ---", C.LogLevel.STAGE)         if not tool_manager.vs_env_paths():             self.bus.log("Visual Studio C++ ortamı bulunamadı.", C.LogLevel.ERR)             messagebox.showerror("Ortam Hatası", "Visual Studio 'Desktop development with C++' bileşeni kurulu değil.")             return False         mode = C.ToolMode(ui.tool_mode.get())         required = ["cmake", "perl", "nasm"]         if ui.generator.get() == C.Generator.NINJA.value:             required.append("ninja")                  missing = []         for tool_key in required:             meta = next(m for m in tool_manager.TOOLS_META if m["key"] == tool_key)             if not tool_manager.detect_tool(meta, mode)[0]:                 missing.append(meta['name'])         if missing:             msg = f"Eksik araçlar: {', '.join(missing)}. Lütfen 'Araç Durumu' sekmesinden kurun." if mode == C.ToolMode.BUNDLED else f"Eksik sistem araçları: {', '.join(missing)}."             self.bus.log(msg, C.LogLevel.ERR)             messagebox.showwarning("Eksik Araçlar", msg)             return False                      if not C._RE_TAG.match(ui.cmb_tag.get() or ""):             self.bus.log("Geçerli MySQL sürümü seçilmedi.", C.LogLevel.ERR)             messagebox.showwarning("Geçersiz Sürüm", "Geçerli bir MySQL sürüm etiketi seçilmedi.")             return False                      try:             free_gb = shutil.disk_usage(C.ROOT).free / (1024**3)             if free_gb &lt; C.DISK_MIN_GB:                 if not messagebox.askyesno("Disk Alanı Uyarısı", f"Yetersiz disk alanı ({free_gb:.1f} GB). En az {C.DISK_MIN_GB} GB önerilir.\nYine de devam edilsin mi?"):                     return False         except OSError as e:             self.bus.log(f"Disk alanı kontrol edilemedi: {e}", C.LogLevel.WARN)                      self.bus.log("--- Uçuş Öncesi Kontroller Başarıyla Tamamlandı ---", C.LogLevel.STAGE)         return True     def clear_cmake_cache(self) -&gt; None:         if self.app._is_busy:             self.bus.log("Derleme çalışırken önbellek temizlenemez.", C.LogLevel.WARN)             return         if not C.BUILD.exists() or not any(C.BUILD.iterdir()):             self.bus.log("'build' klasörü zaten boş.", C.LogLevel.INFO)             messagebox.showinfo("Bilgi", "'build' klasörü zaten boş.")             return         if messagebox.askyesno("Onay", "Tüm 'build' klasörünün içeriği silinecektir. Devam etmek istiyor musunuz?"):             try:                 shutil.rmtree(C.BUILD)                 C.BUILD.mkdir(parents=True, exist_ok=True)                 messagebox.showinfo("Başarılı", "'build' klasörü temizlendi.")             except OSError as e:                 self.bus.log(f"'build' klasörü silinirken hata: {e}", C.LogLevel.ERR)                 messagebox.showerror("Hata", f"Silinemedi:\n{e}")     def fetch_tags_async(self) -&gt; None:         if self.app._is_busy:             return         self.app.task_manager.register(name="MySQL Etiket Listeleme", target=self._fetch_tags_worker, is_build_task=False)              def _fetch_tags_worker(self, task_id: str) -&gt; None:         ui = self.app.ui_manager         tool_manager = self.app.tool_manager         self.bus.log("MySQL GitHub etiketleri çekiliyor...", C.LogLevel.INFO)         tags, latest = [], None         try:             git = tool_manager.which_only_bundled("git") or tool_manager.which("git")             if git:                 with temp_env_path(C.BUNDLED_PATHS):                     out = run_out([git, "ls-remote", "--tags", "https://github.com/mysql/mysql-server.git"])                 versions = []                 for ln in out.splitlines():                     m = re.search(r"refs/tags/(mysql-\d+\.\d+(?:\.\d+)?)$", ln)                     if m:                         t = m.group(1)                         v_match = re.match(r"mysql-(\d+)\.(\d+)(?:\.(\d+))?$", t)                         if v_match:                             v_groups = v_match.groups()                             versions.append(((int(v_groups[0]), int(v_groups[1]), int(v_groups[2] or 0)), t))                 versions.sort(reverse=True)                 tags = [t for _, t in versions[:50]]         except Exception as e:             self.bus.log(f"Git ile etiket çekme hatası: {e}", C.LogLevel.WARN)                      if not tags:             try:                 r = requests.get("https://github.com/mysql/mysql-server/tags", timeout=30)                 r.raise_for_status()                 found = set(re.findall(r"(mysql-\d+\.\d+(?:\.\d+)?)", r.text))                 tags = sorted(list(found), key=lambda t: [int(v or 0) for v in re.match(r"mysql-(\d+)\.(\d+)(?:\.(\d+))?$", t).groups()], reverse=True)[:50]             except requests.RequestException as e:                 self.bus.log(f"API/Scraping ile etiket çekme hatası: {e}", C.LogLevel.ERR)         if not tags:             tags = ["mysql-8.4.0", "mysql-8.0.38"]             self.bus.log("Yedek etiket listesi yüklendi.", C.LogLevel.WARN)                      selected_tag = tags[0] if tags else "mysql-8.4.0"         if ui.last_tag_from_config and ui.last_tag_from_config in tags:             selected_tag = ui.last_tag_from_config         self.bus.log(f"En son MySQL etiketi: {selected_tag}", C.LogLevel.STAGE)         self.app.after(0, lambda: (ui.cmb_tag.config(values=tags), ui.cmb_tag.set(selected_tag)))         self.app.task_manager.task_registry[task_id]['success'] = True     def _fetch_source(self, tag: str, stop_event: threading.Event) -&gt; Path | None:         tool_manager = self.app.tool_manager         dst = C.SRC / tag         if stop_event.is_set():             return None         if dst.exists() and (dst / "extra").exists():             self.bus.log("Kaynaklar zaten çıkarılmış, atlandı.", C.LogLevel.STAGE)             return dst                      zip_path = C.DL / f"{tag}.zip"         download_needed = True         def _zip_ok(zp: Path) -&gt; bool:             try:                 with zipfile.ZipFile(zp) as zf:                     return zf.testzip() is None             except Exception:                 return False         if zip_path.exists():             if _zip_ok(zip_path):                 self.bus.log("Geçerli ZIP arşivi bulundu, indirme atlandı.", C.LogLevel.STAGE)                 download_needed = False             else:                 self.bus.log("Bozuk ZIP arşivi, yeniden indirilecek.", C.LogLevel.WARN)                 zip_path.unlink(missing_ok=True)                  if stop_event.is_set():             return None                      if download_needed:             urls = [f"https://github.com/mysql/mysql-server/archive/refs/tags/{tag}.zip", f"https://codeload.github.com/mysql/mysql-server/zip/refs/tags/{tag}"]             download_ok = False             for u in urls:                 if tool_manager._http_get(u, zip_path, self.bus) and _zip_ok(zip_path):                     download_ok = True                     break             if not download_ok:                 raise RuntimeError("Tüm URL'lerden kaynak ZIP alınamadı.")                  if stop_event.is_set():             return None                      self.bus.status(f"Arşivden çıkarılıyor: {zip_path.name}...")         self.bus.progress("work", 0, "Çıkarma başlıyor...")         if tool_manager._seven_extract(zip_path, C.SRC, self.bus) or tool_manager._python_unzip(zip_path, C.SRC, self.bus):             inner = next((p for p in C.SRC.iterdir() if p.is_dir() and tag.replace('mysql-', '') in p.name), None)             if inner:                 if dst.exists():                     shutil.rmtree(dst, ignore_errors=True)                 inner.rename(dst)                  if not dst.exists():             raise RuntimeError(f"Kaynak çıkarma sonrası klasör bulunamadı: {dst}")         return dst     def _run_cmake(self, args: list[str], need_vs_env: bool, tool_mode: C.ToolMode, stop_event: threading.Event | None) -&gt; tuple[bool, str, Path | None]:         tool_manager = self.app.tool_manager         cmake_exe = tool_manager.which_only_bundled("cmake") if tool_mode == C.ToolMode.BUNDLED else tool_manager.which("cmake")         if not cmake_exe:             self.bus.log("cmake bulunamadı.", C.LogLevel.ERR)             return False, "", None                      vsbat = tool_manager.vs_env_paths() if need_vs_env else None         if need_vs_env and not vsbat:             self.bus.log("VS DevCmd/vcvars64 yok.", C.LogLevel.ERR)             return False, "", None                      env = os.environ.copy()         if tool_mode == C.ToolMode.BUNDLED:             env["PATH"] = ";".join([str(p) for p in C.BUNDLED_PATHS if p.exists()] + [os.environ.get("PATH", "")])                      config_type = next((arg for arg in args if "DCMAKE_BUILD_TYPE" in arg), "unknown").split("=")[-1]         log_path = C.LOGS / f"cmake_configure_{config_type}_{datetime.datetime.now():%Y%m%d-%H%M%S}.log"         self.bus.log(f"CMake yapılandırma çıktısı şu dosyaya loglanıyor: {log_path}", C.LogLevel.STAGE)                  output_lines: list[str] = []         def on_line(s: str) -&gt; None:             output_lines.append(s)                  ok = False         on_start_cb = lambda p: self.add_pid(p.pid)                  if need_vs_env:             bat_path = C.DEPS / "_cmk.bat"             with open(bat_path, "w", encoding="utf-8", newline="\r\n") as f:                 bat_name = Path(vsbat).name.lower()                 f.write(f'@echo off\r\nchcp 65001 &gt; nul\r\n')                 f.write(f'call "{vsbat}" {"x64" if "vcvars" in bat_name else "-no_logo -arch=x64"}\r\n')                 quoted_args = " ".join(f'"{a}"' if " " in a else a for a in args)                 f.write(f'"{cmake_exe}" {quoted_args}\r\n')             ok = stream_proc(f'cmd /d /c "{bat_path}"', env, on_line=on_line, log_file_path=log_path, stop_event=stop_event, on_start=on_start_cb)             bat_path.unlink(missing_ok=True)         else:             ok = stream_proc([cmake_exe, *args], env, on_line=on_line, log_file_path=log_path, stop_event=stop_event, on_start=on_start_cb)                  if not ok:             self.bus.log(f"HATA: CMake yapılandırması başarısız. Detaylar için logu inceleyin:\n--&gt; {log_path}", C.LogLevel.ERR)             if messagebox.askyesno("CMake Hatası", f"CMake yapılandırması başarısız oldu.\n\nDetayları içeren log dosyasını açmak ister misiniz?\n({log_path.name})"):                 try:                     os.startfile(log_path)                 except Exception as e:                     messagebox.showerror("Hata", f"Log dosyası açılamadı:\n{e}")         return ok, "\n".join(output_lines[-200:]), log_path     def _configure_mysql(self, cfg_obj: BuildConfig, stop_event: threading.Event | None) -&gt; bool:         tool_manager = self.app.tool_manager         if cfg_obj.build_type == C.BuildType.RELEASE and not cfg_obj.only_client:             self.bus.log("[Install] Release -&gt; RelWithDebInfo (PDB için gerekli).", C.LogLevel.WARN)             cfg_obj.build_type = C.BuildType.RELWITHDEBINFO                  cfg, bld = cfg_obj.build_type.value, cfg_obj.build_dir         if stop_event and stop_event.is_set():             return False         self.bus.log(f"[{cfg}] Yapılandırma başlıyor → {bld}", C.LogLevel.STAGE)         sccache_path = tool_manager.which_only_bundled('sccache') or tool_manager.which('sccache')         is_ninja = cfg_obj.generator == C.Generator.NINJA         if (bld / "CMakeCache.txt").exists() and ((bld / "build.ninja").exists() if is_ninja else any(bld.glob("*.sln"))):             self.bus.log(f"[{cfg}] Geçerli cache bulundu, configure atlandı.", C.LogLevel.STAGE)             return True         gen_args = (["-G", "Ninja"], True) if is_ninja else (["-G", "Visual Studio 17 2022", "-A", "x64"], True)         cmake_rt = ("MultiThreaded" if cfg_obj.runtime == C.Runtime.MT else "MultiThreadedDLL") + ("Debug" if cfg in (C.BuildType.DEBUG.value, C.BuildType.RELWITHDEBINFO.value) else "")                  common_args = ["-DWITH_SSL=system", *self._openssl_hint_args(cfg_obj.ssl_root_dir)]         if cfg_obj.cxx_version not in ("Latest", ""):             common_args.extend([f"-DCMAKE_CXX_STANDARD={cfg_obj.cxx_version}", "-DCMAKE_CXX_STANDARD_REQUIRED=ON"])         if sccache_path:             common_args.extend([f"-DCMAKE_C_COMPILER_LAUNCHER={sccache_path}", f"-DCMAKE_CXX_COMPILER_LAUNCHER={sccache_path}"])         if cfg_obj.runtime == C.Runtime.MT:             common_args.append("-DSTATIC_MSVCRT=ON")         if cfg_obj.extra_defs.strip():             common_args.extend(d for d in re.split(r"\s+", cfg_obj.extra_defs.strip()) if d.startswith("-D"))                      args = [*gen_args[0], "-S", str(cfg_obj.source_dir), "-B", str(bld), f"-DCMAKE_BUILD_TYPE={cfg}",                 f"-DCMAKE_INSTALL_PREFIX={cfg_obj.install_dir}", f"-DCMAKE_MSVC_RUNTIME_LIBRARY={cmake_rt}", *common_args]                  ok, tail, _ = self._run_cmake(args, gen_args[1], cfg_obj.tool_mode, stop_event)         if ok and not ((bld/"build.ninja").exists() if is_ninja else any(bld.glob("*.sln"))):             self.bus.log(f"[{cfg}] HATA: CMake hatasız bitti ama derleme dosyaları (build.ninja/sln) OLUŞTURULMADI.", C.LogLevel.ERR)             messagebox.showerror("CMake Hatası", "CMake başarıyla tamamlandı ancak derleme dosyaları (örn. build.ninja) oluşturulmadı. Lütfen Visual Studio kurulumunuzu ve CMake loglarını kontrol edin.")             ok = False         if not ok:             self.bus.log(f"\n--- [{cfg}] CMake Son 200 Satır ---\n{tail}\n---------------------------\n", C.LogLevel.ERR)         return ok     def _build_mysql(self, cfg_obj: BuildConfig, task_id: str, stop_event: threading.Event | None) -&gt; tuple[bool, bool, str]:         ui = self.app.ui_manager         tool_manager = self.app.tool_manager         cfg, bld = cfg_obj.build_type.value, cfg_obj.build_dir         is_ninja = cfg_obj.generator == C.Generator.NINJA         if stop_event and stop_event.is_set():             return False, False, "stopped"         if not bld.exists() or not ((bld/"build.ninja").exists() if is_ninja else any(bld.glob("*.sln"))):             self.bus.log(f"[{cfg}] HATA: Derleme başlatılamıyor, yapılandırma dosyaları eksik.", C.LogLevel.ERR)             return False, False, "config_missing"                  cmake_exe = tool_manager.which_only_bundled("cmake") if cfg_obj.tool_mode == C.ToolMode.BUNDLED else tool_manager.which("cmake")         if not cmake_exe:             self.bus.log("cmake bulunamadı.", C.LogLevel.ERR)             return False, False, ""         env = os.environ.copy()         jobs = str(max(1, min(C.DEFAULT_JOBS, cfg_obj.jobs)))         env["CL"] = f"/MP{jobs}"         env["CMAKE_BUILD_PARALLEL_LEVEL"] = jobs         target = "mysqlclient" if cfg_obj.only_client else "install"         self.bus.log(f"[{cfg}] Derleme başlıyor: target={target}, jobs={jobs}", C.LogLevel.STAGE)                  multi_rules = False         def on_line(s: str) -&gt; None:             nonlocal multi_rules             ui.update_build_stats(s, task_id)             if C._RE_ERR_MULTI_RULE.search(s):                 multi_rules = True                  ok = False         on_start_cb = lambda p: self.add_pid(p.pid)         if is_ninja:             vsbat = tool_manager.vs_env_paths()             bat_path = C.DEPS/f"_build_{cfg.lower()}.bat"             with open(bat_path, "w", encoding="utf-8", newline="\r\n") as f:                 bat_name = Path(vsbat).name.lower()                 f.write(f'@echo off\r\nchcp 65001 &gt; nul\r\n')                 f.write(f'call "{vsbat}" {"x64" if "vcvars" in bat_name else "-no_logo -arch=x64"}\r\n')                 f.write(f'"{cmake_exe}" --build "{bld}" --config "{cfg}" --parallel {jobs} --target {target}\r\n')             ok = stream_proc(f'cmd /d /c "{bat_path}"', env, on_line=on_line, stop_event=stop_event, on_start=on_start_cb)             bat_path.unlink(missing_ok=True)         else:             args = [cmake_exe, "--build", str(bld), "--config", cfg, "--parallel", jobs, "--target", target]             ok = stream_proc(args, env, on_line=on_line, stop_event=stop_event, on_start=on_start_cb)         self.bus.log(f"[{cfg}] Derleme sonucu: {'OK' if ok else 'FAIL'}", C.LogLevel.STAGE if ok else C.LogLevel.ERR)         if not ok and multi_rules:             return False, True, "multi_rules"         if ok:             self._post_build_copy(cfg_obj)         return ok, False, ""     def _post_build_copy(self, cfg_obj: BuildConfig) -&gt; None:         try:             ssl_lib, inst_lib = cfg_obj.ssl_root_dir / "lib", cfg_obj.install_dir / "lib"             inst_lib.mkdir(parents=True, exist_ok=True)             if ssl_lib.exists():                 for dll in ssl_lib.glob("*.dll"):                     shutil.copy2(dll, inst_lib)             if cfg_obj.only_client:                 lib_file = next(cfg_obj.build_dir.rglob("mysqlclient.lib"), None) or next(cfg_obj.build_dir.rglob("libmysql.lib"), None)                 if lib_file:                     shutil.copy2(lib_file, inst_lib / "mysqlclient.lib")                 src_inc, inst_inc = cfg_obj.source_dir / "include", cfg_obj.install_dir / "include"                 if src_inc.exists():                     shutil.copytree(src_inc, inst_inc, dirs_exist_ok=True)         except Exception as e:             self.bus.log(f"Nihai kopyalama işleminde hata: {e}", C.LogLevel.WARN)     def _openssl_hint_args(self, root: Path) -&gt; list[str]:         inc, lib = root / "include", root / "lib"         libssl, libcrypto = next(lib.glob("libssl*.lib"), None), next(lib.glob("libcrypto*.lib"), None)         args = [f"-DOPENSSL_ROOT_DIR={root}", f"-DOPENSSL_INCLUDE_DIR={inc}"]         if libssl and libcrypto:             args.extend([f"-DOPENSSL_LIBRARIES={libssl};{libcrypto}", f"-DOPENSSL_CRYPTO_LIBRARY={libcrypto}", f"-DOPENSSL_SSL_LIBRARY={libssl}"])         applink = root / "ms" / "applink.c"         if applink.exists():             args.append(f"-DOPENSSL_APPLINK_C={applink}")         return args          def confirm_reinstall_openssl(self) -&gt; None:         if messagebox.askyesno("Onay", "Mevcut OpenSSL derlemeleri silinip kaynak kodundan yeniden derlenecektir. Bu işlem zaman alabilir.\n\nDevam etmek istiyor musunuz?"):             self.build_openssl_async(reinstall=True)     def build_openssl_async(self, reinstall: bool) -&gt; None:         if self.app._is_busy:             return         self.app.set_busy(True)         stop_event = threading.Event()         self.app.task_manager.register(name=f"OpenSSL Kurulumu", target=self._openssl_action_worker, args=(reinstall, stop_event), stop_event=stop_event, is_build_task=False)     def _openssl_action_worker(self, reinstall: bool, stop_event: threading.Event, task_id: str) -&gt; None:         ui = self.app.ui_manager         success = False         try:             rt = C.Runtime(ui.runtime.get())             self.bus.log(f"OpenSSL derlemesi başlatılıyor (Runtime: {rt.value}, Yeniden Kur: {reinstall})", C.LogLevel.STAGE)             success = self._build_openssl_sequence(rt, stop_event, reinstall)             if not stop_event.is_set():                 messagebox.showinfo("OpenSSL", f"OpenSSL kurulumu {'başarıyla' if success else 'hatalarla'} tamamlandı.")         except Exception as e:             if not stop_event.is_set():                 self.bus.log(f"OpenSSL kurulumunda kritik hata: {e}\n{traceback.format_exc()}", C.LogLevel.ERR)                 messagebox.showerror("Hata", f"OpenSSL kurulumu başarısız oldu: {e}")         finally:             self.app.task_manager.task_registry[task_id]['success'] = success             self.app.set_busy(False)     def _build_openssl_sequence(self, runtime: C.Runtime, stop_event: threading.Event, reinstall: bool = False) -&gt; bool:         if stop_event.is_set():             return False         if not self._openssl_tool_check():             raise RuntimeError("OpenSSL için gerekli araçlar eksik.")         self._ensure_openssl_source(reinstall)         success = True         for build_type in (C.BuildType.RELEASE, C.BuildType.DEBUG):             if stop_event.is_set():                 success = False                 break             try:                 self._build_single_openssl_cfg(runtime, build_type, reinstall, stop_event)             except Exception as e:                 success = False                 self.bus.log(f"OpenSSL {build_type.value} ({runtime.value}) derlemesinde hata: {e}", C.LogLevel.ERR)         self.app.after(0, self.refresh_ssl_status)         return success     def _ensure_openssl_source(self, reinstall: bool = False) -&gt; None:         tool_manager = self.app.tool_manager         zip_path = C.DL / "openssl.zip"         if reinstall:             self.bus.log("Yeniden kurulum: Eski OpenSSL kaynakları ve arşivi siliniyor...", C.LogLevel.WARN)             zip_path.unlink(missing_ok=True)             shutil.rmtree(C.OPENSSL_SRC, ignore_errors=True)                  C.OPENSSL_SRC.mkdir(parents=True, exist_ok=True)         if any(p.is_dir() and (p / "Configure").exists() for p in C.OPENSSL_SRC.glob("openssl-openssl-*")):             return                      if not zip_path.exists():             self.bus.status("OpenSSL kaynak ZIP indiriliyor...")             tool_manager._http_get(C.URLS["openssl"], zip_path, self.bus)                      self.bus.status("OpenSSL kaynakları çıkarılıyor...")         if not (tool_manager._seven_extract(zip_path, C.OPENSSL_SRC, self.bus) or tool_manager._python_unzip(zip_path, C.OPENSSL_SRC, self.bus)):             raise RuntimeError("OpenSSL arşivi açılamadı.")     def _build_single_openssl_cfg(self, runtime: C.Runtime, build_type: C.BuildType, reinstall: bool, stop_event: threading.Event) -&gt; None:         ui = self.app.ui_manager         tool_manager = self.app.tool_manager         if stop_event.is_set():             return         root = BuildConfig._openssl_root(runtime, build_type)         if not reinstall and self._openssl_ok(root):             self.bus.log(f"OpenSSL {build_type.value} ({runtime.value}) zaten derlenmiş, atlandı.", C.LogLevel.STAGE)             return                  self.bus.log(f"--- OpenSSL {build_type.value} ({runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE)         if reinstall and root.exists():             shutil.rmtree(root, ignore_errors=True)             root.mkdir(parents=True, exist_ok=True)                      tree = next(C.OPENSSL_SRC.glob("openssl-openssl-*"), None)         if not tree or not (tree / "Configure").exists():             raise RuntimeError("OpenSSL kaynak klasörü/Configure betiği bulunamadı.")                      vsbat = tool_manager.vs_env_paths()         perl_exe = tool_manager.which_only_bundled("perl") or tool_manager.which("perl")         if not vsbat or not perl_exe:             raise RuntimeError("VS Ortamı veya Perl bulunamadı.")                      cl_flag = ("/MT" if runtime == C.Runtime.MT else "/MD") + ("d" if build_type == C.BuildType.DEBUG else "")         jobs = max(1, min(128, ui.jobs.get()))         bat_path = C.DEPS / f"_ossl_{runtime.value}_{build_type.value.lower()}.bat"         prefix_path = root         openssldir_path = root / "ssl"         with open(bat_path, "w", encoding="utf-8", newline="\r\n") as f:             bat_name = Path(vsbat).name.lower()             f.write(f'@echo off\r\nchcp 65001 &gt; nul\r\n')             f.write(f'call "{vsbat}" {"x64" if "vcvars" in bat_name else "-no_logo -arch=x64"}\r\n')             f.write(f'nmake clean &gt; nul 2&gt;&amp;1\r\nset CL={cl_flag} /MP{jobs}\r\n')             f.write(f'"{perl_exe}" Configure VC-WIN64A no-tests no-zlib --prefix="{prefix_path}" --openssldir="{openssldir_path}"\r\nif errorlevel 1 exit /b 10\r\n')             f.write(f'nmake\r\nif errorlevel 1 exit /b 11\r\n')             f.write(f'nmake install_sw\r\nif errorlevel 1 exit /b 12\r\n')                      env = os.environ.copy()         if C.ToolMode(ui.tool_mode.get()) == C.ToolMode.BUNDLED:             env["PATH"] = ";".join([str(p) for p in C.BUNDLED_PATHS if p.exists()] + [env.get('PATH', '')])                  on_start_cb = lambda p: self.add_pid(p.pid)         ok = stream_proc(f'cmd /d /c "{bat_path}"', env, on_line=lambda s: self.bus.log(s, C.LogLevel.CMD), stop_event=stop_event, cwd=str(tree), on_start=on_start_cb)         bat_path.unlink(missing_ok=True)         if stop_event.is_set() or not ok:             raise RuntimeError(f"OpenSSL {build_type.value} derlemesi başarısız oldu.")                      self._ensure_applink_stub(root)         if not self._openssl_ok(root):             raise RuntimeError(f"OpenSSL {build_type.value} derlemesi sonrası doğrulama başarısız.")                      self.bus.log(f"✔️ OpenSSL {build_type.value} ({runtime.value}) derlemesi tamamlandı.", C.LogLevel.STAGE)     def _openssl_tool_check(self) -&gt; bool:         tool_manager = self.app.tool_manager         ui = self.app.ui_manager         mode = C.ToolMode(ui.tool_mode.get())         missing = []         for tool_key in ("perl", "nasm"):             meta = next(m for m in tool_manager.TOOLS_META if m["key"] == tool_key)             if not tool_manager.detect_tool(meta, mode)[0]:                 missing.append(meta['name'])         if missing:             msg = f"OpenSSL derlemesi için eksik araçlar: {', '.join(missing)}. {'Lütfen önce bunları kurun.' if mode == C.ToolMode.BUNDLED else 'Lütfen sisteme yükleyip PATH\'e ekleyin.'}"             self.bus.log(msg, C.LogLevel.ERR)             messagebox.showwarning("Eksik OpenSSL Araçları", msg)             return False         return True     def refresh_ssl_status(self) -&gt; None:         ui = self.app.ui_manager         ui.ssl_tree.delete(*ui.ssl_tree.get_children())         selected_rt = C.Runtime(ui.runtime.get())         for rt in C.Runtime:             rt_id = ui.ssl_tree.insert("", "end", values=(f"► Runtime: {rt.value} (Seçili: {'EVET' if selected_rt == rt else 'HAYIR'})", "—"), tags=("rt_summary",))             for bt in (C.BuildType.RELEASE, C.BuildType.DEBUG):                 root = BuildConfig._openssl_root(rt, bt)                 status = "✔️ OK" if self._openssl_ok(root) else "❌ Eksik"                 cfg_id = ui.ssl_tree.insert(rt_id, "end", values=(f"  └─ {bt.value} Yapılandırması", status), tags=("cfg_summary",))                 for name, path in (("ssl.h",root/"include/openssl/ssl.h"), ("libssl.lib",root/"lib/libssl.lib"), ("libcrypto.lib",root/"lib/libcrypto.lib")):                     ui.ssl_tree.insert(cfg_id, "end", values=(f"    ├─ {name}", "Var" if path.exists() else "Yok"))         ui.ssl_tree.tag_configure("rt_summary", font="-weight bold -size 9")         ui.ssl_tree.tag_configure("cfg_summary", font="-weight normal -size 9")     def _openssl_ok(self, root: Path) -&gt; bool:         return (root/"include/openssl/ssl.h").exists() and (root/"lib/libssl.lib").exists() and (root/"lib/libcrypto.lib").exists()     def _ensure_applink_stub(self, root: Path) -&gt; Path:         ms = root/"ms"         ms.mkdir(exist_ok=True)         dst = ms/"applink.c"         if not dst.exists():             dst.write_text("/* applink stub */\n#ifndef OPENSSL_NO_APPLINK\n#define OPENSSL_NO_APPLINK 1\n#endif\n", encoding="utf-8")         return dst     def initial_trash_cleanup(self) -&gt; None:         if any(C.TRASH.iterdir()):             self.bus.log("Başlangıç: Dolu .trash klasörü bulundu. Arka planda temizleniyor...", C.LogLevel.WARN)             for item in C.TRASH.iterdir():                 self._cleanup_trash_folder_async(item)     def _cleanup_trash_folder_async(self, folder: Path) -&gt; None:         self.app.task_manager.register(name=f".trash Temizliği ({folder.name})", target=shutil.rmtree, args=(folder,), kwargs={'ignore_errors': True}, is_build_task=False)     def _do_clean_start(self) -&gt; None:         self.bus.log("Temiz başlangıç: Klasörler .trash'e taşınıyor...", C.LogLevel.STAGE)         for p in (C.SRC, C.BUILD, C.INST):             if p.exists() and any(p.iterdir()):                 trash_dest = C.TRASH / f"{p.name}-{int(time.time())}"                 try:                     shutil.move(str(p), str(trash_dest))                     self.bus.log(f"Taşındı: {p} -&gt; {trash_dest}", C.LogLevel.CMD)                 except OSError as e:                     self.bus.log(f"'{p}' taşınamadı, hata: {e}. Lütfen elle silin.", C.LogLevel.ERR)                     raise                 p.mkdir(exist_ok=True)                 self._cleanup_trash_folder_async(trash_dest)          def empty_trash_folder(self):         if not C.TRASH.exists() or not any(C.TRASH.iterdir()):             messagebox.showinfo("Bilgi", ".trash klasörü zaten boş.")             return         if messagebox.askyesno("Onay", ".trash klasörünün tüm içeriği kalıcı olarak silinecektir. Bu işlem geri alınamaz.\n\nDevam etmek istiyor musunuz?"):             self.bus.log(".trash klasörü temizleniyor...", C.LogLevel.WARN)             for item in C.TRASH.iterdir():                 self._cleanup_trash_folder_async(item)             self.bus.log(".trash temizleme görevi başlatıldı.", C.LogLevel.INFO) class App(ttk.Window):     def __init__(self):         super().__init__(themename="litera", title="MySQL Builder Pro v1.0.0 - turkmmo.com - dormammu", size=(1320, 920), minsize=(1200, 860))         self.process = psutil.Process()         self.q: queue.Queue = queue.Queue()         self._is_busy = False                  self.bus = Bus(self)         self.task_manager = TaskManager(self)         self.tool_manager = ToolManager(self)         self.build_manager = BuildManager(self)                  self.ui_manager = UIManager(self)         self.after(100, self.ui_manager.pump_queue)         self.after(10, self.tool_manager.refresh_tools)         self.after(10, self.build_manager.refresh_ssl_status)         self.after(10, self.build_manager.fetch_tags_async)         self.after(500, self.build_manager.initial_trash_cleanup)         self.after(1000, self.ui_manager.update_resource_monitor)         self.after(1000, self.task_manager.update_monitor)         self.after(1100, self.ui_manager.update_trash_monitor)         self.ui_manager.runtime.trace_add("write", lambda *a: self.build_manager.refresh_ssl_status())         self.protocol("WM_DELETE_WINDOW", self.on_closing)     def on_closing(self) -&gt; None:         if self._is_busy:             if messagebox.askyesno("Çıkış Onayı", "Bir işlem çalışıyor. Çıkmak istediğinizden emin misiniz?"):                 self.task_manager.stop_all()                 self.ui_manager.save_settings()                 self.destroy()         else:             self.ui_manager.save_settings()             self.destroy()     def set_busy(self, busy: bool) -&gt; None:         self._is_busy = busy         self.ui_manager.set_controls_state('disabled' if busy else 'normal')         try:             priority_class = psutil.IDLE_PRIORITY_CLASS if busy else psutil.NORMAL_PRIORITY_CLASS             io_priority = psutil.IOPRIO_LOW if busy else psutil.IOPRIO_NORMAL             self.process.nice(priority_class)             if hasattr(self.process, "ionice"):                 self.process.ionice(io_priority)             self.bus.log(f"Uygulama önceliği {'düşürüldü' if busy else 'normale döndü'}.", C.LogLevel.INFO)         except psutil.Error as e:             self.bus.log(f"Uygulama önceliği ayarlanırken hata: {e}", C.LogLevel.WARN) if __name__ == "__main__":     for p in (C.LOGS, C.TOOLS, C.DL, C.SRC, C.BUILD, C.INST, C.DEPS, C.TRASH, C.OPENSSL_BASE, C.OPENSSL_SRC):         p.mkdir(parents=True, exist_ok=True)     app = App()     app.mainloop()

MySQL Builder Pro ile Metin2 Geliştirme Sürecini Kolaylaştırın
Metin2 özel sunucu geliştirme sürecinde karşılaşılan en yaygın sorunlardan birisi derleme süreçleridir. Özellikle C++ tabanlı sistemlerde, MySQL bağlantıları sırasında 'mysqlclient.lib' hatası gibi derleme sorunları geliştiricileri zorlayabilir. Bu yazıda, [Dev Paylaşım] MySQL mysqlclient.lib Derleme Sorununa Son! adlı projeyi tanıtarak, bu sorunu kalıcı olarak nasıl çözeceğinizi anlatıyoruz.

Metin2 Geliştirme Ortamında MySQL Entegrasyonu
Metin2 özel sunucularında genellikle auth server ve game server olmak üzere iki temel yapı bulunur. Bu yapılar genellikle MySQL veritabanı ile haberleşir. Ancak, özellikle C++ kaynak kodlar üzerinde çalışırken MySQL bağlantısı sırasında bazı derleme hatalarıyla karşılaşılabilir. Bunlardan birisi 'mysqlclient.lib' dosyasının bulunamaması veya eksik derlenmesidir. Bu durum, proje derlenirken linker hatasına neden olur.

MySQL Builder Pro Nedir?
MySQL Builder Pro, Metin2 özel sunucu geliştiricileri için özel olarak tasarlanmış bir araçtır. Bu aracın temel amacı, MySQL kütüphanelerini doğru şekilde derlemek ve geliştiricilere kolayca entegre edilebilir bir yapı sunmaktır. mysqlclient.lib dosyasının derleme sürecini otomatikleştirerek, geliştiricilerin zaman kaybetmeden çalışmaya devam etmelerini sağlar.

mysqlclient.lib Derleme Sorunu Neden Oluşur?
C++ projelerinde MySQL ile bağlantı kurarken, derleyici mysqlclient.lib dosyasını bekler. Bu dosya, MySQL istemci kütüphanesinin derlenmiş halidir. Eğer doğru bir şekilde yapılandırılmazsa, linker hatası alınır. Bu durum özellikle farklı MySQL sürümleriyle çalışılırken daha sık ortaya çıkar. Ayrıca, 32-bit ve 64-bit sistem uyumsuzlukları da bu hatayı tetikleyebilir.

MySQL Builder Pro ile Çözüm
MySQL Builder Pro, bu sorunu adım adım otomatikleştirerek çözer. Kullanıcı dostu arayüzü sayesinde, sadece birkaç tıklama ile mysqlclient.lib dosyası doğru şekilde derlenebilir. Bu sayede Metin2 sunucularında auth-server ve game-server bağlantıları daha hızlı ve güvenli bir şekilde sağlanabilir. Arayüz üzerinden MySQL versiyon seçimi, derleme türü (debug/release) ve sistem mimarisi (x86/x64) ayarlanabilmektedir.

Metin2 Sunucu Geliştirme İçin Önerilen Yapılandırmalar
Metin2 özel sunucu geliştirme yaparken dikkat etmeniz gereken bazı önemli konfigürasyonlar vardır. Öncelikle, MySQL sürümünüzle derleme sırasında kullandığınız kütüphane sürümlerinin uyumlu olması gerekir. MySQL Builder Pro bu uyumu sağlamanız için rehberlik eder. Ayrıca, Visual Studio sürümü, Windows SDK ve compiler ayarları da önemli rol oynar. mysqlclient.lib dosyasının doğru yola yerleştirilmesi, linker ayarlarının yapılması ve include dizinlerinin belirlenmesi gibi işlemler, MySQL Builder Pro sayesinde otomatik hale getirilmiştir.

Metin2 Geliştiricileri İçin Kaynak Kod Paylaşımları
Metin2 geliştirme dünyasında kaynak kod paylaşımı, topluluk tarafından büyük önem taşır. Martysama gibi isimlerin sağladığı C++ sistemler, Python GUI uygulamaları ve Py Root yapıları, birçok geliştiriciye ilham vermiştir. MySQL Builder Pro da bu doğrultuda, geliştiricilerin daha az derleme stresi yaşaması için geliştirilmiş değerli bir araçtır. Bu tip paylaşımlar sayesinde, Metin2 özel sunucu geliştirme süreci hız kazanır ve hata oranı düşer.

Sonuç
Metin2 özel sunucu geliştirme sürecinde mysqlclient.lib gibi derleme hataları, geliştirici motivasyonunu düşürebilir. Ancak, MySQL Builder Pro gibi araçlar sayesinde bu tür sorunlar minimize edilebilir. Hem yeni başlayanlar hem de deneyimli geliştiriciler için büyük kolaylık sağlayan bu araç, Metin2 gelişimine katkı sağlarken, geliştirme süresini kısaltmaktadır. Eğer Metin2 özel sunucu geliştiriyorsanız ve MySQL ile ilgili derleme sorunları yaşıyorsanız, mutlaka MySQL Builder Pro'yu denemelisiniz.

Kaynak: Metin2 Lobby


Simplify Your Metin2 Development Process with MySQL Builder Pro
One of the most common issues encountered during Metin2 private server development is related to compilation processes. Especially in C++ based systems, errors like 'mysqlclient.lib' during MySQL connections can be challenging for developers. In this article, we introduce the project [Dev Sharing] End to MySQL mysqlclient.lib Compilation Issue! and explain how to permanently resolve this issue.

MySQL Integration in Metin2 Development Environment
In Metin2 private servers, two main structures usually exist: the auth server and the game server. These structures often communicate with MySQL databases. However, while working on C++ source codes, certain compilation errors may occur during MySQL connections. One such error is the failure to locate or incorrectly compile the 'mysqlclient.lib' file, which causes a linker error during project compilation.

What is MySQL Builder Pro?
MySQL Builder Pro is a tool specifically designed for Metin2 private server developers. Its main purpose is to correctly compile MySQL libraries and provide an easily integrable structure for developers. By automating the compilation process of the mysqlclient.lib file, it allows developers to continue their work without wasting time.

Why Does the mysqlclient.lib Compilation Error Occur?
When connecting to MySQL in C++ projects, the compiler expects the mysqlclient.lib file. This file is the compiled version of the MySQL client library. If not configured properly, a linker error occurs. This situation arises more frequently when working with different MySQL versions. Additionally, 32-bit and 64-bit system incompatibilities can trigger this error.

Solution with MySQL Builder Pro
MySQL Builder Pro resolves this issue by automatically streamlining the process with just a few clicks. Thanks to its user-friendly interface, the mysqlclient.lib file can be compiled correctly in no time. This enables faster and safer connections between auth-server and game-server in Metin2 servers. Users can select MySQL versions, compilation types (debug/release), and system architectures (x86/x64) through the interface.

Recommended Configurations for Metin2 Server Development
There are several important configurations to consider when developing Metin2 private servers. First, ensure that your MySQL version matches the library versions used during compilation. MySQL Builder Pro guides you in achieving this compatibility. Additionally, the Visual Studio version, Windows SDK, and compiler settings play crucial roles. With MySQL Builder Pro, tasks such as placing the mysqlclient.lib file in the correct path, setting up linker options, and defining include directories are automated.

Source Code Sharing for Metin2 Developers
In the world of Metin2 development, sharing source code is highly valued by the community. Systems provided by figures like Martysama, including C++ systems, Python GUI applications, and Py Root structures, have inspired many developers. MySQL Builder Pro, following this direction, helps reduce compilation stress for developers. Such shares accelerate the development process and decrease error rates in Metin2 private server creation.

Conclusion
Compilation errors like mysqlclient.lib during Metin2 private server development can lower developer motivation. However, tools like MySQL Builder Pro can minimize these issues. Providing great convenience for both newcomers and experienced developers, this tool contributes to Metin2's development while shortening the overall development timeline. If you're developing Metin2 private servers and experiencing MySQL-related compilation issues, give MySQL Builder Pro a try.

Source: Metin2 Lobby
 

Forumdan daha fazla yararlanmak için giriş yapın yada üye olun!

Forumdan daha fazla yararlanmak için giriş yapın veya kayıt olun!

Kaydol

Forumda bir hesap oluşturmak tamamen ücretsizdir.

Üye ol
Giriş Yap

Eğer bir hesabınız var ise lütfen giriş yapın

Giriş Yap

Tema düzenleyici

Tema özelletirmeleri