romegames 1
romegames
Krutzo 1
Krutzo
shrpnl 1
shrpnl
Best Studio 1
Best Studio
D 1
delimuratt
Aliyldrim 1
Aliyldrim
Mt2Hizmet 1
Mt2Hizmet
noisiv 1
noisiv
Manwe Work 1
Manwe Work
Hikaye Ekle
Reklam vermek için turkmmo@gmail.com

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

  • Konuyu başlatan Konuyu başlatan dormammu
  • Başlangıç tarihi Başlangıç tarihi
  • Cevaplar Cevaplar 2
  • Görüntüleme Görüntüleme 267

dormammu

Level 4
TM Üye
Üye
Katılım
6 Eyl 2017
Konular
53
Mesajlar
358
Çözüm
1
Online süresi
1mo 8d
Reaksiyon Skoru
169
Altın Konu
1
TM Yaşı
8 Yıl 9 Ay 3 Gün
Başarım Puanı
129
Yaş
32
MmoLira
6,460
DevLira
78
Ticaret - 100%
1   0   0

ROHAN2 WORLD 1-120 TR TİPİ OFFICIAL YOHARA, BALATHOR VE AMON! 80. GÜNÜNDE! +10.000 ONLİNE! HİLE VE BOT %100 ENGELLİ HEMEN TIKLA!

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:

  • Tam Otomasyon: Git, CMake, Ninja, Perl gibi gerekli tüm araçları kendisi indirir ve kurar.
  • Grafik Arayüz: Tüm ayarları (Release/Debug, MT/MD, MySQL sürümü) kolayca seçmenizi sağlar.
  • OpenSSL Desteği: Derleme için zorunlu olan OpenSSL kütüphanesini kaynak kodundan sizin için derler ve yapılandırır.
  • Hata Kontrolü: Süreci anlık olarak log ekranından takip edebilir, olası hataları anında görebilirsiniz.
  • 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. İndirme: linkinden "Community" sürümünü indirin.
  2. 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:
    • C++ ile masaüstü geliştirme (Desktop development with C++)
    • 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. İndirme: linkinden en güncel Python sürümünü (örn: 3.12.x) indirin.
  2. 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. Windows'ta Başlat menüsünü açın.
  2. cmd yazarak "Komut İstemi"ni (Command Prompt) çalıştırın.
  3. Açılan siyah ekrana aşağıdaki komutu kopyalayıp yapıştırın ve Enter'a basın:
    Python:
    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. Sağ taraftaki "Eksikleri Kur" butonuna tıklayın.
  2. 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.
  3. İş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. "OpenSSL Durumu" paneline bakın.
  2. Sağdaki "OpenSSL Kur (R+D)" butonuna tıklayın.
  3. 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.

  • 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.
  • Runtime (ÇOK ÖNEMLİ):
    • 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.
    • 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.
  • Seçenekler:
    • Release Derle: Canlı sunucular için kullanacağınız optimize edilmiş .lib dosyasını oluşturur. (İşaretli kalmalı)
    • Debug Derle: Hata ayıklama modunda derleme yapar. Geliştiriciler için gereklidir. (İsteğe bağlı)
    • 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. Tüm ayarları yaptıktan sonra sağ alttaki yeşil "Başlat" butonuna tıklayın.
  2. 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.
  3. Derleme başarıyla tamamlandığında bir bildirim alacaksınız.
  4. "Install Klasörünü Aç" butonuna tıklayın.
  5. 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


[CODE lang="python" title="MySQL Builder Pro v1"]# -*- 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) -> Path: return C.BUILD / self.runtime.value / self.tag / self.build_type.value.lower().replace(' ', '_')
@property
def install_dir(self) -> Path: return C.INST / self.runtime.value / self.tag / self.build_type.value.lower().replace(' ', '_')
@property
def ssl_root_dir(self) -> Path: return self._openssl_root(self.runtime, self.build_type)

@staticmethod
def _openssl_root(runtime: C.Runtime, build_type: C.BuildType) -> 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) -> None: self.q.put(("log", message, level.value))
def progress(self, kind: str, pc: int, text: str = "") -> None: self.q.put((kind, pc, text))
def status(self, text: str) -> None: self.q.put(("status", text))
def update_task_progress(self, task_id: str, progress_text: str) -> 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) -> 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) -> 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) -> None:
match = self._RE_PERCENT.search(line)
if match:
pc = int(match.group(1))
if pc > 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> Any:
self.control_widgets.append(widget)
return widget

def _create_config_widgets(self, parent: ttk.Frame) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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("<Button-3>", 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) -> 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) -> 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]) -> None:
full_text_to_insert = ""
is_scrolled_to_bottom = self.log.yview()[1] >= 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 > 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) -> 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) -> 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) -> 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 > 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) -> 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 < 1024:
size_str = f"{total_size} B"
elif total_size < 1024**2:
size_str = f"{total_size/1024:.1f} KB"
elif total_size < 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) -> 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) -> 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) -> None:
self.log.delete("1.0", END)

def _save_log(self) -> 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) -> 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) -> 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) -> None:
self.log.tag_config(tag_name, elide=not is_visible)

def show_log_context_menu(self, event: tk.Event) -> 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) -> 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) -> 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) -> 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) -> str | None:
return shutil.which(name)

def which_only_bundled(self, name: str) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> None:
with self.pids_lock:
self.active_pids.add(pid)

def remove_pid(self, pid: int) -> None:
with self.pids_lock:
self.active_pids.discard(pid)

def get_active_pids(self) -> set[int]:
with self.pids_lock:
return self.active_pids.copy()

def clear_active_pids(self) -> None:
with self.pids_lock:
self.active_pids.clear()

def start_build(self) -> 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) -> 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) -> 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) -> 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) -> 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 < 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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 > 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--> {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) -> 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 -> 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) -> 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) -> 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 > 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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 > 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 > nul 2>&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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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} -> {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) -> 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) -> 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()[/CODE]
 
Paylaşım için teşekkürler.
 
Paylaşım için teşekkürler.
 

Şu an konuyu görüntüleyenler (Toplam : 0, Üye: 0, Misafir: 0)

Geri
Üst