- Katılım
- 6 Mayıs 2022
- Konular
- 48,258
- Mesajlar
- 48,568
- Tepkime puanı
- 74
- M2 Yaşı
- 3 yıl 11 ay 10 gün
- Trophy Puan
- 48
- M2 Yang
- 488,549
GÜNCELLENDİ
Selamlar Turkmmo ailesi,
Metin2 source derleme sürecinin en baş ağrıtan kısımlarından birinin kütüphaneleri (library) derlemek olduğunu hepimiz biliyoruz. libjpeg, DevIL, Crypto++ gibi temel bağımlılıkları doğru ayarlarla, doğru mimaride (32-bit client için!) derlemek, özellikle bu işe yeni başlayanlar için tam bir kabusa dönüşebiliyor.
Forumda "kütüphane hatası alıyorum", "build edemiyorum" gibi onlarca konu gördükten sonra bu süreci tamamen otomatikleştiren bir araç geliştirdim! Artık tek bir programla, hiçbir ayarla boğuşmadan, tüm kütüphanelerinizi dakikalar içinde hazır hale getirebileceksiniz.
💣 Bu Araç Ne İşe Yarıyor? Neden Kullanmalısınız? 💣
Bu program, Metin2 client ve server derlemek için gereken en temel 3 kütüphaneyi sizin için sıfırdan oluşturur.
- <li data-xf-list-type="ul">Zahmetsiz Kurulum: Derleme için gereken tüm programları (Git, CMake, Ninja vb.) tek tıkla kendisi indirir. Size sadece Visual Studio kurmak kalır. <li data-xf-list-type="ul">Metin2 Uyumlu: Client için zorunlu olan 32-bit (x86) mimarisini ve isteğe bağlı olarak 64-bit (x64) mimarisini destekler. <li data-xf-list-type="ul">Tam Otomasyon: "Tüm Kütüphaneleri Derle" butonu ile bağımlılıkları (DevIL için libjpeg gibi) kendi gözeterek tüm kütüphaneleri sırayla derler. <li data-xf-list-type="ul">Düzenli ve Hazır Çıktı: Derlenen tüm dosyaları (.lib, .h, .dll) alıp doğrudan source'unuzun extern klasörüne atabileceğiniz şekilde hazırlar. Artık extern/include ve extern/lib klasörlerini aramakla uğraşmak yok!
⚠️ Kurulum Öncesi GEREKSİNİMLER (MUTLAKA OKUYUN!) ⚠️
Programı çalıştırmadan önce sisteminizde iki şeyin kurulu ve ayarlı olması ŞARTTIR!
<h4>1. Visual Studio 2022 (C++ İş Yükü)</h4>
Bu adım olmadan program KESİNLİKLE ÇALIŞMAZ!
- <li data-xf-list-type="ul">Visual Studio 2022 kurulu olmalı. <li data-xf-list-type="ul">Visual Studio Installer üzerinden "Masaüstü geliştirme (C++)" (Desktop development with C++) iş yükünün seçili olduğundan emin olun. Bu, derleyicinin kendisidir.
<h4>2. Python ve Gerekli Paketler</h4>
Bu araç Python ile yazılmıştır. Bu yüzden sisteminizde Python kurulu olmalıdır.
- <li data-xf-list-type="ul">Python Sürümü: Python 3.14 veya daha yenibir sürüm gereklidir.
- <li data-xf-list-type="ul">PowerShell'i açıp python --version komutuyla sürümünüzü kontrol edebilirsiniz. <li data-xf-list-type="ul">Kurulu değilse,
Ziyaretçiler için gizlenmiş link,görmek için üye olmalısınız! Giriş yap veya üye ol.indirip kurarken "Add Python to PATH" seçeneğini işaretlemeyi UNUTMAYIN!
Kod:pip install requests psutil ttkbootstrap - <li data-xf-list-type="ul">PowerShell'i açıp python --version komutuyla sürümünüzü kontrol edebilirsiniz. <li data-xf-list-type="ul">Kurulu değilse,
🚀 Kurulum ve Çalıştırma (SIFIRDAN ANLATIM) 🚀
İndirme linki yok! Kodu doğrudan buraya yapıştıracağım. Aşağıdaki adımları sırayla takip edin:
1. Boş Bir Klasör Oluşturun:Masaüstünüzde veya istediğiniz bir yerde LibBuilder gibi bir isimle yeni bir klasör oluşturun.
2. Metin Belgesi Oluşturun:Oluşturduğunuz bu LibBuilder klasörünün içine girin. Sağ tıklayıp Yeni -> Metin Belgesi deyin.
3. Dosyanın Adını ve Uzantısını Değiştirin:Oluşan Yeni Metin Belgesi.txt dosyasının adını LibBuilder.py olarak değiştirin.
- <li data-xf-list-type="ul">DİKKAT: Dosya uzantılarının görünür olduğundan emin olun! Eğer .txt kısmını göremiyorsanız, klasör seçeneklerinden "Bilinen dosya türleri için uzantıları gizle" seçeneğinin tikini kaldırın. Dosyanın adı tam olarak derleyici.py olmalıdır.
5. Programı Çalıştırın:
- <li data-xf-list-type="ul">LibBuilder klasörünün içindeyken, üstteki adres çubuğuna powershell yazıp Enter'a basın. Bu, o klasörde bir PowerShell penceresi açacaktır. <li data-xf-list-type="ul">Açılan PowerShell ekranına aşağıdaki komutu yazın ve Enter'a basın:
Kod:
python .\LibBuilder.py
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, field 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 Arch(str, Enum): X86 = "x86" X64 = "x64" class Runtime(str, Enum): MT = "MT" MD = "MD" class BuildType(str, Enum): RELEASE = "Release" DEBUG = "Debug" class ToolMode(str, Enum): BUNDLED = "bundled" SYSTEM = "system" 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" CONFIG_FILE = ROOT / "config.json" INST_LIBJPEG_TURBO = INST / "libjpeg-turbo" INST_DEVIL = INST / "devil" INST_CRYPTOPP = INST / "cryptopp" INST_LZO = INST / "lzo" INST_SPDLOG = INST / "spdlog" INST_NLOHMANN_JSON = INST / "nlohmann-json" INST_PYTHON = INST / "python" LIB_META = { "libjpeg-turbo": {"name": "libjpeg-turbo", "type": "git", "repo": "https://github.com/libjpeg-turbo/libjpeg-turbo", "tag_prefix": "", "tag_filter": re.compile(r"^\d+\.\d+\.\d+$")}, "devil": {"name": "DevIL", "type": "git", "repo": "https://github.com/DentonW/DevIL", "tag_prefix": "v", "tag_filter": re.compile(r"^v\d+\.\d+\.\d+$")}, "cryptopp": {"name": "Crypto++", "type": "git", "repo": "https://github.com/weidai11/cryptopp", "tag_prefix": "CRYPTOPP_", "tag_filter": re.compile(r"^CRYPTOPP_\d+_\d+(_\d+)?$")}, "lzo": {"name": "LZO", "type": "url", "url": "https://www.oberhumer.com/opensource/lzo/download/lzo-2.10.tar.gz", "version": "2.10"}, "spdlog": {"name": "spdlog", "type": "git", "repo": "https://github.com/gabime/spdlog", "tag_prefix": "v", "tag_filter": re.compile(r"^v\d+\.\d+\.\d+$")}, "nlohmann-json": {"name": "nlohmann/json", "type": "git", "repo": "https://github.com/nlohmann/json", "tag_prefix": "v", "tag_filter": re.compile(r"^v\d+\.\d+\.\d+$")}, "python": {"name": "Python", "type": "git", "repo": "https://github.com/python/cpython", "tag_prefix": "v", "tag_filter": re.compile(r"^v\d+\.\d+(\.\d+)?$")}, } BUILD_ORDER = ["libjpeg-turbo", "devil", "cryptopp", "lzo", "spdlog", "nlohmann-json", "python"] DEFAULT_JOBS = max(1, (os.cpu_count() or 4) - 2) DISK_MIN_GB = 2.0 MAX_LOG_LINES = 5000 _RE_NINJA = re.compile(r"\[(\d+)\s*/\s*(\d+)\]") 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", "nasm": "https://www.nasm.us/pub/nasm/releasebuilds/2.16.03/win64/nasm-2.16.03-win64.zip", } BUNDLED_PATHS = [TOOLS, TOOLS / "7zip", TOOLS / "cmake" / "bin", TOOLS / "mingit" / "cmd", TOOLS / "nasm"] @dataclass class BuildConfig: lib_key: str tag: str runtime: C.Runtime tool_mode: C.ToolMode jobs: int prefer_clean: bool architectures: list[C.Arch] build_configs: list[C.BuildType] extra_opts: dict = field(default_factory=dict) @property def source_dir(self) -> Path: return C.SRC / self.lib_key / self.tag def build_dir_for(self, arch: C.Arch, build_type: C.BuildType) -> Path: return C.BUILD / self.lib_key / self.tag / arch.value / self.runtime.value / build_type.value.lower() def install_dir_for(self, arch: C.Arch, build_type: C.BuildType) -> Path: return getattr(C, f"INST_{self.lib_key.upper().replace('-', '_')}") / arch.value / self.runtime.value / build_type.value.lower() 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)) def work_mode(self, mode: str): self.q.put(("work_mode", mode)) def work_control(self, action: str): self.q.put(("work_control", action)) def work_text(self, text: str): self.q.put(("work_text", text)) @contextlib.contextmanager def temp_env_path(extra_paths: list[Path]): old_path = os.environ.get("PATH", "") new_path = ";".join([str(p) for p in extra_paths if p.exists()] + [old_path]) 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=30).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 = subprocess.CREATE_NO_WINDOW | (subprocess.BELOW_NORMAL_PRIORITY_CLASS if low_pri else 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) 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() active_pids = self.app.build_manager.get_active_pids() if active_pids: for pid in active_pids: try: psutil.Process(pid).kill() except psutil.Error: pass 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.lib_tabs: dict[str, dict[str, Any]] = {} 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) common_panel = ttk.Labelframe(parent, text="Ortak Derleme Ayarları", padding=10); common_panel.grid(row=0, column=0, sticky="ew") self.notebook = ttk.Notebook(parent, padding=0); self.notebook.grid(row=1, column=0, sticky="nsew", pady=(5,0)) self._create_common_panel_content(common_panel) for lib_key, meta in C.LIB_META.items(): tab = ttk.Frame(self.notebook, padding=10) self.notebook.add(tab, text=meta["name"]) self.lib_tabs[lib_key] = {"_tab_id": tab} self._create_library_tab(tab, lib_key) tools_panel = ttk.Labelframe(parent, text="Araç Durumu", padding=10); tools_panel.grid(row=2, column=0, sticky="ew", pady=(10,0)) dash = ttk.Labelframe(parent, text="İşlem Durumu", padding=10); dash.grid(row=3, column=0, sticky="ew", pady=(10, 0)) self._create_tools_panel(tools_panel) self._create_status_panel_content(dash) def _create_library_tab(self, parent: ttk.Frame, lib_key: str): parent.grid_columnconfigure(1, weight=1) f_ver = ttk.Frame(parent); f_ver.grid(row=0, column=0, columnspan=3, sticky="ew", pady=2) meta = C.LIB_META[lib_key] ttk.Label(f_ver, text=f"{meta['name']} Sürümü:", width=20).pack(side=LEFT) if meta.get('type') == 'url': version_str = meta.get('version', 'Sabit Sürüm') ttk.Label(f_ver, text=version_str, font="-weight bold").pack(side=LEFT, padx=5, fill=X, expand=True) self.lib_tabs[lib_key]['fixed_version'] = version_str else: cmb_tag = self._register_control(ttk.Combobox(f_ver, width=25, state="readonly")) cmb_tag.pack(side=LEFT, padx=5, fill=X, expand=True) self.lib_tabs[lib_key]['cmb_tag'] = cmb_tag btn_fetch = ttk.Button(f_ver, text="Listele", command=lambda k=lib_key, c=cmb_tag: self.app.build_manager.fetch_tags_async(k, c), width=8, bootstyle=(SECONDARY, OUTLINE)) btn_fetch.pack(side=LEFT) f_opts = ttk.Labelframe(parent, text="Özel Ayarlar", padding=5); f_opts.grid(row=1, column=0, columnspan=3, sticky="nsew", pady=(10,0)) opts_dict = {} if lib_key in ['libjpeg-turbo', 'devil', 'lzo', 'python']: var_static = self._register_control(ttk.BooleanVar(value=True)) var_shared = self._register_control(ttk.BooleanVar(value=True)) self._register_control(ttk.Checkbutton(f_opts, text="Statik Kütüphane (.lib) Derle", variable=var_static)).pack(anchor=W) self._register_control(ttk.Checkbutton(f_opts, text="Dinamik Kütüphane (.dll) Derle", variable=var_shared)).pack(anchor=W) opts_dict = {'static': var_static, 'shared': var_shared} else: ttk.Label(f_opts, text="Bu kütüphane için özel bir ayar bulunmuyor.").pack(padx=5, pady=5) self.lib_tabs[lib_key]['opts'] = opts_dict def _create_common_panel_content(self, parent: ttk.Frame): parent.grid_columnconfigure(1, weight=1); parent.grid_columnconfigure(3, weight=1) ttk.Label(parent, text="Runtime:").grid(row=0, 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(parent, textvariable=self.runtime, values=[r.value for r in C.Runtime], width=10, state="readonly")).grid(row=0, column=1, sticky="ew", padx=5) ttk.Label(parent, text="Paralel İş:").grid(row=0, column=2, sticky="w", padx=(15, 5), pady=2) self.jobs = self._register_control(ttk.IntVar(value=C.DEFAULT_JOBS)) self._register_control(ttk.Spinbox(parent, from_=1, to=128, textvariable=self.jobs, width=6)).grid(row=0, column=3, sticky="ew", padx=5) ttk.Label(parent, text="Araç Modu:").grid(row=1, column=0, sticky="w", padx=5, pady=2) f_tools = ttk.Frame(parent); f_tools.grid(row=1, column=1, sticky="w") 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) ttk.Label(parent, text="Mimari:").grid(row=1, column=2, sticky="w", padx=(15, 5), pady=2) f_arch = ttk.Frame(parent); f_arch.grid(row=1, column=3, sticky="w") self.build_x64 = self._register_control(ttk.BooleanVar(value=True)) self._register_control(ttk.Checkbutton(f_arch, text="x64 (64-bit)", variable=self.build_x64, bootstyle=PRIMARY)).pack(side=LEFT) self.build_x86 = self._register_control(ttk.BooleanVar(value=True)) self._register_control(ttk.Checkbutton(f_arch, text="x86 (32-bit)", variable=self.build_x86, bootstyle=PRIMARY)).pack(side=LEFT, padx=10) f_opts = ttk.Frame(parent); f_opts.grid(row=0, column=4, rowspan=2, sticky="e", padx=(20,0)) self.build_release = self._register_control(ttk.BooleanVar(value=True)) self._register_control(ttk.Checkbutton(f_opts, 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(f_opts, text="Debug Derle", variable=self.build_debug, bootstyle=PRIMARY)).pack(anchor="w", pady=1) self.prefer_clean = self._register_control(ttk.BooleanVar(value=False)) self._register_control(ttk.Checkbutton(f_opts, text="Temiz başlangıç", variable=self.prefer_clean, bootstyle=PRIMARY)).pack(anchor="w", pady=1) def _create_tools_panel(self, parent: ttk.Frame) -> None: parent.grid_columnconfigure(0, weight=1) self.tree = ttk.Treeview(parent, columns=("tool", "status", "ver", "src", "path"), show="headings", height=4, bootstyle=INFO) for c, t, w in (("tool","Araç",120), ("status","Durum",80), ("ver","Sürüm",100), ("src","Kaynak",70), ("path","Yol",300)): self.tree.heading(c, text=t); self.tree.column(c, width=w, anchor=W) self.tree.grid(row=0, column=0, sticky="nsew") btn_frame = ttk.Frame(parent); 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_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="nse", padx=(20, 0)) self.btn_start = ttk.Button(btns, text="Seçili Kütüphaneyi Derle", bootstyle=SUCCESS, width=22, command=self.app.build_manager.start_build); self.btn_start.pack(pady=2, fill=X) self.btn_build_all = ttk.Button(btns, text="Tüm Kütüphaneleri Derle", bootstyle=(SUCCESS, OUTLINE), width=22, command=self.app.build_manager.start_build_all); self.btn_build_all.pack(pady=2, fill=X) self.btn_stop = ttk.Button(btns, text="Durdur (Acil)", bootstyle=(DANGER, OUTLINE), width=22, 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="Tüm 'build' Klasörünü Temizle", bootstyle=(WARNING, OUTLINE), width=22, command=self.app.build_manager.clear_build_folder); 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=22, command=self._open_install_folder); self.btn_open_install.pack(pady=2, fill=X) self.btn_info = ttk.Button(btns, text="ℹ️ Bilgi & Yardım", command=self._show_info_modal, bootstyle=(SECONDARY, OUTLINE)); self.btn_info.pack(fill=X, pady=(8, 0)) 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") 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(): self.log.tag_config(level.value, foreground=fg, background=bg) 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: try: while not self.app.q.empty(): kind, *rest = self.app.q.get_nowait() if kind == "log": timestamp = datetime.datetime.now().strftime('%H:%M:%S') message, level = cast(str, rest[0]), cast(str, rest[1]) is_scrolled_to_bottom = self.log.yview()[1] >= 1.0 for ln in message.splitlines(): start_index = self.log.index("end-1c") self.log.insert(END, f"[{timestamp}] {ln}\n") end_index = self.log.index("end-1c") self.log.tag_add(level, start_index, end_index) if is_scrolled_to_bottom: self.log.see(END) 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 elif kind == "work_mode": self.pb_work.config(mode=rest[0]) elif kind == "work_control" and rest[0] == "start": self.pb_work.start() elif kind == "work_control" and rest[0] == "stop": self.pb_work.stop() elif kind == "work_text": self.lbl_work_txt.config(text=rest[0]) except queue.Empty: pass finally: self.app.after(100, self.pump_queue) def set_controls_state(self, state: str) -> None: self.btn_start.config(state=state) self.btn_build_all.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 self.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: total_mb_s = (current_io.read_bytes - self.last_disk_io.read_bytes + current_io.write_bytes - self.last_disk_io.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): pass 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**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.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.build_release.set(settings.get("build_release", True)) self.build_debug.set(settings.get("build_debug", True)) self.build_x64.set(settings.get("build_x64", True)) self.build_x86.set(settings.get("build_x86", True)) self.prefer_clean.set(settings.get("prefer_clean", False)) for lib_key, tab_widgets in self.lib_tabs.items(): meta = C.LIB_META.get(lib_key, {}) if meta.get('type') != 'url': last_tag = settings.get(f"last_tag_{lib_key}") if last_tag: tab_widgets['last_tag_from_config'] = last_tag 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 = { "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(), "build_x64": self.build_x64.get(), "build_x86": self.build_x86.get(), "prefer_clean": self.prefer_clean.get() } for lib_key, tab_widgets in self.lib_tabs.items(): if 'cmb_tag' in tab_widgets: tag = tab_widgets['cmb_tag'].get() if tag: settings[f"last_tag_{lib_key}"] = tag 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 _open_install_folder(self) -> None: C.INST.mkdir(parents=True, exist_ok=True) try: os.startfile(C.INST) except Exception as e: 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 = """Lib Builder Pro v7.4 (Final) Kullanım Rehberi ============================================ Bu araç libjpeg-turbo, DevIL, Crypto++, LZO, spdlog ve nlohmann/json kütüphanelerini Windows ortamında 32-bit ve 64-bit olarak derleme/kurma sürecini otomatikleştirmek için tasarlanmıştır. --- ÖN GEREKSİNİMLER --- - **Windows 10/11 (64-bit)** - **Visual Studio 2022:** "Masaüstü geliştirme (C++)" iş yükünün kurulu olması ZORUNLUDUR. --- ADIM ADIM KULLANIM --- 1. **Araç Kurulumu:** Uygulamayı ilk açtığınızda "Araç Durumu" panelinden **"Eksikleri Kur"** butonuna tıklayarak gerekli araçları (CMake, Ninja vb.) kurun. 2. **Derleme Ayarları:** - Sürüm seçimi gerektiren kütüphaneler için derlemek istediğiniz sürümü **"Listele"** butonu ile seçin. - **Ortak Ayarlar** bölümünden `Runtime`, `Mimari` ve `Release/Debug` gibi seçenekleri belirleyin. 3. **Derlemeyi Başlatma:** - **"Seçili Kütüphaneyi Derle":** Aktif olan sekmeyi derler. - **"Tüm Kütüphaneleri Derle":** Tüm kütüphaneleri sırayla (bağımlılıkları gözeterek) derler. 4. **Sonuçlar:** Derleme bittiğinde, dosyalar `install/<kütüphane_adı>/<mimari>/...` klasörüne kaydedilir. --- KÜTÜPHANEYE ÖZEL NOTLAR --- - **DevIL:** Bu kütüphane, libjpeg-turbo'ya bağımlıdır. "Tümünü Derle" seçeneği bu sırayı otomatik yönetir. Tek tek derliyorsanız, **önce libjpeg-turbo'yu derlemeniz GEREKİR.** - **Crypto++, spdlog, Python:** MSBuild/CMake ile statik kütüphane (.lib) olarak derlenirler. - **LZO:** CMake ile derlenir. - **nlohmann/json:** Bu kütüphane sadece başlık dosyalarından oluşur (header-only). Derleme işlemi yerine sadece ilgili dosyalar `install` klasörüne kopyalanır. """ 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) 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": ["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":"nasm", "name":"NASM", "exe":"nasm", "ensure":lambda bus, se: self._ensure_zip_tool("nasm", C.URLS["nasm"], "nasm", bus, se), "vercmd":["nasm", "-v"]}, ] def vs_env_path(self) -> str | None: vswhere = Path(r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe") if vswhere.exists(): cmd = [str(vswhere), "-latest", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-property", "installationPath"] base_path = run_out(subprocess.list2cmdline(cmd)).splitlines() base = Path((base_path[0] if base_path else "").strip()) if base.is_dir(): candidate = base / "VC/Auxiliary/Build/vcvarsall.bat" if candidate.exists(): return str(candidate) for ver in ("2022", "2019"): for ed in ("Community", "Professional", "Enterprise", "BuildTools"): p = Path(rf"C:\Program Files\Microsoft Visual Studio\{ver}\{ed}\VC\Auxiliary\Build\vcvarsall.bat") if p.exists(): return str(p) return None def which(self, name: str, bundled_only: bool) -> str | None: paths = C.BUNDLED_PATHS if bundled_only else (os.environ.get("PATH", "").split(os.pathsep) + [str(p) for p in C.BUNDLED_PATHS]) for folder in paths: if not folder: continue p = Path(folder) if not p.is_dir(): continue for ext in ("", ".exe"): fp = p / (name + ext) if fp.exists() and fp.is_file(): return str(fp) 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 = next((path for exe in executables if (path := self.which(exe, mode == C.ToolMode.BUNDLED))), None) if not p and mode == C.ToolMode.SYSTEM: p = next((path for exe in executables if (path := shutil.which(exe))), None) if not p: return None, None, None src = "Bundled" if Path(p).resolve().is_relative_to(C.TOOLS.resolve()) else "System" vercmd = [p] + meta.get("vercmd", ["--version"])[1:] out = run_out(vercmd, cwd=Path(p).parent) if out.startswith("ERR:"): return p, "Hata", src ver = next((m.group(1) for pat in [r"v?(\d+\.\d+(?:\.\d+)?)", r"version[\s:]*(\d+\.\d+(?:\.\d+)?)"] if (m := re.search(pat, out, re.I))), out.splitlines()[0][:40]) return p, 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) ui.tree.insert("", END, iid=meta["key"], values=(meta["name"], "✔️ Yüklü" if path else "❌ Yok", ver or "-", src or "-", path or "-"), tags=() if path else ("missing",)) 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. Devam 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 = [m for m in self.TOOLS_META if not self.detect_tool(m, C.ToolMode.BUNDLED)[0]] if missing else self.TOOLS_META if not metas_to_install and missing: self.bus.log("Tüm araçlar zaten .tools klasöründe yüklü.", C.LogLevel.INFO) messagebox.showinfo("Araçlar", "Eksik araç bulunamadı."); return with ThreadPoolExecutor() as executor: futures = [executor.submit(m["ensure"], self.bus, stop_event) for m in metas_to_install if "ensure" in m] 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(): 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 _http_get(self, url: str, dest: Path, bus: Bus) -> bool: bus.log(f"[DL] Başlıyor: {Path(url).name}", C.LogLevel.STAGE) dest.parent.mkdir(parents=True, exist_ok=True) try: with requests.get(url, stream=True, timeout=60, verify=True) as r: r.raise_for_status() total_size = int(r.headers.get("Content-Length", 0)) done, last_pc = 0, -1 with open(dest, "wb") as f: for chunk in r.iter_content(256 * 1024): 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"); return True except requests.exceptions.SSLError as e: bus.log(f"[DL] SSL Hatası: {e}. Sunucu sertifikasıyla ilgili bir sorun olabilir.", C.LogLevel.ERR) return False except requests.RequestException as e: bus.log(f"[DL] Hata: {e}", C.LogLevel.ERR) return False def _seven_extract(self, arc: Path, out: Path, bus: Bus) -> bool: seven = self.which("7z", False) or self.which("7zr", False) if not seven: try: self._ensure_7zip(bus, None) seven = self.which("7z", False) or self.which("7zr", False) except Exception as e: bus.log(f"7-Zip otomatik olarak kurulamadı: {e}", C.LogLevel.ERR) if not seven: bus.log("7-Zip (7z.exe or 7zr.exe) bulunamadı.", C.LogLevel.ERR) return False parser = SevenZipProgressParser(bus) cmd = f'"{seven}" x "{arc}" -o"{out}" -y -bsp1' return stream_proc(cmd, os.environ.copy(), on_line=parser.parse_line) 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 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() and 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) 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ı") 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 (zdir / "7z.exe").exists(): return 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) if not (zdir / "7z.exe").exists(): raise RuntimeError("7-Zip extraction failed. 7z.exe was not found after extraction.") 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: self.active_pids.add(pid) def remove_pid(self, pid: int) -> None: self.active_pids.discard(pid) def get_active_pids(self) -> set[int]: return self.active_pids.copy() def clear_active_pids(self) -> None: self.active_pids.clear() def _get_common_build_config_parts(self) -> dict | None: ui = self.app.ui_manager architectures = [arch for arch, var in [(C.Arch.X64, ui.build_x64), (C.Arch.X86, ui.build_x86)] if var.get()] if not architectures: messagebox.showwarning("Seçim Gerekli", "En az bir mimari (x86/x64) seçin."); return None build_configs = [btype for btype, var in [(C.BuildType.RELEASE, ui.build_release), (C.BuildType.DEBUG, ui.build_debug)] if var.get()] if not build_configs: messagebox.showwarning("Seçim Gerekli", "En az bir derleme konfigürasyonu (Release/Debug) seçin."); return None return { "runtime": C.Runtime(ui.runtime.get()), "tool_mode": C.ToolMode(ui.tool_mode.get()), "jobs": ui.jobs.get(), "prefer_clean": ui.prefer_clean.get(), "architectures": architectures, "build_configs": build_configs } def start_build(self) -> None: if self.app._is_busy: return ui = self.app.ui_manager selected_tab_id = ui.notebook.index(ui.notebook.select()) if selected_tab_id is None: messagebox.showwarning("Seçim Gerekli", "Lütfen derlemek için bir kütüphane sekmesi seçin."); return lib_key = list(C.LIB_META.keys())[selected_tab_id] meta = C.LIB_META[lib_key] common_parts = self._get_common_build_config_parts() if not common_parts: return tab_widgets = ui.lib_tabs.get(lib_key) tag = "" if meta.get('type') == 'url': tag = meta.get('version') else: tag = tab_widgets['cmb_tag'].get() if not tag: messagebox.showwarning("Seçim Gerekli", f"{meta['name']} için bir sürüm seçin."); return extra_opts = {key: var.get() for key, var in tab_widgets.get('opts', {}).items()} config = BuildConfig(lib_key=lib_key, tag=tag, **common_parts, extra_opts=extra_opts) stop_event = threading.Event() self.app.task_manager.register( name=f"Ana Derleme Yöneticisi ({meta['name']})", target=self._build_manager_worker, args=([config], stop_event), stop_event=stop_event, is_build_task=False ) def start_build_all(self) -> None: if self.app._is_busy: return ui = self.app.ui_manager common_parts = self._get_common_build_config_parts() if not common_parts: return configs_to_build = [] for lib_key in C.BUILD_ORDER: meta = C.LIB_META[lib_key] tab_widgets = ui.lib_tabs.get(lib_key) tag = "" if meta.get('type') == 'url': tag = meta.get('version') else: tag = tab_widgets['cmb_tag'].get() if not tag: messagebox.showwarning("Seçim Gerekli", f"Tümünü derlemek için önce {meta['name']} sekmesinden bir sürüm seçmelisiniz."); return extra_opts = {key: var.get() for key, var in tab_widgets.get('opts', {}).items()} configs_to_build.append(BuildConfig(lib_key=lib_key, tag=tag, **common_parts, extra_opts=extra_opts)) stop_event = threading.Event() self.app.task_manager.register( name="Tüm Kütüphaneleri Derleme Yöneticisi", target=self._build_manager_worker, args=(configs_to_build, stop_event), stop_event=stop_event, is_build_task=False ) def _build_manager_worker(self, configs: list[BuildConfig], stop_event: threading.Event, task_id: str) -> None: self.app.set_busy(True) self.app.ui_manager.log.delete("1.0", END) overall_success = True try: if configs and configs[0].prefer_clean: self._do_clean_start() for config in configs: if stop_event.is_set(): overall_success = False; break self.bus.log(f"====== {C.LIB_META[config.lib_key]['name'].upper()} İÇİN DERLEME SÜRECİ BAŞLIYOR ======", C.LogLevel.STAGE) if not self._run_preflight_checks(config): overall_success = False; break success = self._build_single_library(config, stop_event) if not success: overall_success = False self.bus.log(f"{C.LIB_META[config.lib_key]['name']} derlemesi başarısız oldu. Tüm işlemler durduruluyor.", C.LogLevel.ERR) break 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) messagebox.showerror("Kritik Hata", f"Beklenmedik hata:\n\n{e}") overall_success = False finally: self._finalize_build_process(overall_success) self.app.task_manager.task_registry[task_id]['success'] = overall_success self.app.set_busy(False) def _build_single_library(self, config: BuildConfig, stop_event: threading.Event) -> bool: source_dir = self._fetch_source(config.lib_key, config.tag, stop_event) if not source_dir: return False build_actions = { "libjpeg-turbo": self._build_libjpeg, "devil": self._build_devil, "cryptopp": self._build_cryptopp, "lzo": self._build_lzo, "spdlog": self._build_spdlog, "nlohmann-json": self._build_nlohmann_json, "python": self._build_python } action = build_actions.get(config.lib_key) if not action: return False for arch in config.architectures: if stop_event.is_set(): return False self.bus.log(f"===== {arch.value.upper()} MİMARİSİ İÇİN DERLEME BAŞLIYOR =====", C.LogLevel.STAGE) if not action(config, source_dir, arch, stop_event): return False return True def _run_command_in_vs_env(self, cmd_list: list[str], cwd: Path, log_name_prefix: str, arch: C.Arch, stop_event: threading.Event, on_line: Callable[[str],None]=None) -> bool: vsbat = self.app.tool_manager.vs_env_path() if not vsbat: self.bus.log("VS Geliştirici Ortamı (vcvarsall.bat) bulunamadı.", C.LogLevel.ERR); return False arch_param = "amd64" if arch == C.Arch.X64 else "x86" log_path = C.LOGS / f"{log_name_prefix}_{arch.value}_{datetime.datetime.now():%Y%m%d-%H%M%S}.log" self.bus.log(f"Çıktı şu dosyaya loglanıyor: {log_path.name}", C.LogLevel.STAGE) bat_path = C.DEPS / f"_{log_name_prefix}_runner.bat" with open(bat_path, "w", encoding="utf-8", newline="\r\n") as f: f.write(f'@echo off\r\nchcp 65001 > nul\r\n') f.write(f'call "{vsbat}" {arch_param}\r\n') f.write(f'{" ".join(cmd_list)}\r\n') env = os.environ.copy() if self.app.ui_manager.tool_mode.get() == C.ToolMode.BUNDLED: env["PATH"] = ";".join([str(p) for p in C.BUNDLED_PATHS if p.exists()] + [env.get('PATH', '')]) ok = stream_proc(f'cmd /d /c "{bat_path}"', env, on_line=on_line if on_line else lambda s: self.bus.log(s, C.LogLevel.CMD), log_file_path=log_path, stop_event=stop_event, cwd=cwd, on_start=lambda p: self.add_pid(p.pid)) bat_path.unlink(missing_ok=True) if not ok: self.bus.log(f"Komut başarısız oldu. Detaylar için logu inceleyin: {log_path}", C.LogLevel.ERR) return ok def _build_libjpeg(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -> bool: for build_type in config.build_configs: if stop_event.is_set(): return False self.bus.log(f"--- libjpeg-turbo ({arch.value} / {build_type.value} / {config.runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE) build_dir = config.build_dir_for(arch, build_type); install_dir = config.install_dir_for(arch, build_type) cmake_exe = self.app.tool_manager.which("cmake", config.tool_mode == C.ToolMode.BUNDLED) if not cmake_exe: return False cmake_args = [f'"{cmake_exe}"', "-S", f'"{source_dir}"', "-B", f'"{build_dir}"', "-G", "Ninja", f"-DCMAKE_INSTALL_PREFIX={install_dir}", f"-DCMAKE_BUILD_TYPE={build_type.value}", f"-DENABLE_STATIC={'ON' if config.extra_opts.get('static') else 'OFF'}", f"-DENABLE_SHARED={'ON' if config.extra_opts.get('shared') else 'OFF'}", f"-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded{'Debug' if build_type == C.BuildType.DEBUG else ''}{'DLL' if config.runtime == C.Runtime.MD else ''}"] if not self._run_command_in_vs_env(cmake_args, source_dir, f"ljt_config_{build_type.value.lower()}", arch, stop_event): return False build_args = [f'"{cmake_exe}"', "--build", f'"{build_dir}"', "--config", build_type.value, "--target", "install", "--parallel", str(config.jobs)] if not self._run_command_in_vs_env(build_args, source_dir, f"ljt_build_{build_type.value.lower()}", arch, stop_event, on_line=lambda s: self.app.ui_manager.update_build_stats(s, 'build_task')): return False return True def _patch_devil_rc_file(self, source_dir: Path) -> bool: rc_file = source_dir / "DevIL" / "src-IL" / "msvc" / "IL.rc" if not rc_file.exists(): self.bus.log(f"DevIL RC dosyası bulunamadı, patch atlanıyor: {rc_file}", C.LogLevel.WARN) return True try: self.bus.log(f"DevIL RC dosyası ({rc_file.name}) MFC bağımlılığını kaldırmak için yamalanıyor...", C.LogLevel.STAGE) content = rc_file.read_text(encoding='cp1252', errors='ignore') if '#include "afxres.h"' in content: content = content.replace('#include "afxres.h"', '// #include "afxres.h" -- Patched by LibBuilder') rc_file.write_text(content, encoding='cp1252') self.bus.log("Yama başarıyla uygulandı.", C.LogLevel.INFO) else: self.bus.log("afxres.h bağımlılığı bulunamadı, yama gerekli değil.", C.LogLevel.INFO) return True except Exception as e: self.bus.log(f"DevIL RC dosyası yamalanırken hata: {e}", C.LogLevel.ERR) return False def _build_devil(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -> bool: if not self._patch_devil_rc_file(source_dir): return False for build_type in config.build_configs: if stop_event.is_set(): return False self.bus.log(f"--- DevIL ({arch.value} / {build_type.value} / {config.runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE) build_dir = config.build_dir_for(arch, build_type); install_dir = config.install_dir_for(arch, build_type) ljt_install_dir = C.INST_LIBJPEG_TURBO / arch.value / config.runtime.value / build_type.value.lower() ljt_lib = ljt_install_dir / "lib" / "jpeg-static.lib"; ljt_inc = ljt_install_dir / "include" if config.extra_opts.get('static') and not (ljt_lib.exists() and ljt_inc.exists()): msg = f"DevIL bağımlılığı (libjpeg-turbo statik) bulunamadı: {ljt_install_dir}. Lütfen önce libjpeg-turbo'yu aynı ayarlarla ({arch.value}/{config.runtime.value}/{build_type.value}) statik olarak derleyin." self.bus.log(msg, C.LogLevel.ERR); messagebox.showerror("Bağımlılık Hatası", msg); return False cmake_exe = self.app.tool_manager.which("cmake", config.tool_mode == C.ToolMode.BUNDLED) if not cmake_exe: return False cmake_source_dir = source_dir / "DevIL" if not (cmake_source_dir / "CMakeLists.txt").exists(): self.bus.log(f"DevIL için CMakeLists.txt beklenen yolda bulunamadı: {cmake_source_dir}", C.LogLevel.ERR) return False cmake_args = [f'"{cmake_exe}"', "-S", f'"{cmake_source_dir}"', "-B", f'"{build_dir}"', "-G", "Ninja", f"-DCMAKE_INSTALL_PREFIX={install_dir}", f"-DCMAKE_BUILD_TYPE={build_type.value}", f"-DBUILD_STATIC_LIBS={'ON' if config.extra_opts.get('static') else 'OFF'}", f"-DBUILD_SHARED_LIBS={'ON' if config.extra_opts.get('shared') else 'OFF'}", f"-DJPEG_INCLUDE_DIR={ljt_inc}", f"-DJPEG_LIBRARY={ljt_lib}", "-DIL_USE_JPEG=ON", "-DIL_USE_PNG=OFF", "-DIL_USE_TIFF=OFF", "-DIL_USE_LCMS=OFF", "-DIL_USE_JASPER=OFF", f"-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded{'Debug' if build_type == C.BuildType.DEBUG else ''}{'DLL' if config.runtime == C.Runtime.MD else ''}"] if not self._run_command_in_vs_env(cmake_args, cmake_source_dir, f"devil_config_{build_type.value.lower()}", arch, stop_event): return False build_args = [f'"{cmake_exe}"', "--build", f'"{build_dir}"', "--config", build_type.value, "--target", "install", "--parallel", str(config.jobs)] if not self._run_command_in_vs_env(build_args, cmake_source_dir, f"devil_build_{build_type.value.lower()}", arch, stop_event, on_line=lambda s: self.app.ui_manager.update_build_stats(s, 'build_task')): return False return True def _build_cryptopp(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -> bool: vs_env = self.app.tool_manager.vs_env_path() if not vs_env: return False msbuild_exe = Path(vs_env).parent.parent.parent.parent / "MSBuild/Current/Bin/MSBuild.exe" if not msbuild_exe.exists(): self.bus.log(f"MSBuild.exe bulunamadı: {msbuild_exe}", C.LogLevel.ERR); return False sln_file = source_dir / "cryptest.sln" if not sln_file.exists(): self.bus.log(f"Crypto++ solution dosyası bulunamadı: {sln_file}", C.LogLevel.ERR); return False for build_type in config.build_configs: if stop_event.is_set(): return False self.bus.log(f"--- Crypto++ ({arch.value} / {build_type.value} / {config.runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE) install_dir = config.install_dir_for(arch, build_type) runtime_flag = "MultiThreaded" if config.runtime == C.Runtime.MT else "MultiThreadedDLL" if build_type == C.BuildType.DEBUG: runtime_flag += "Debug" platform = "x64" if arch == C.Arch.X64 else "Win32" build_args = [f'"{msbuild_exe}"', f'"{sln_file}"', "/t:cryptlib", f"/p:Configuration={build_type.value}", f"/p:Platform={platform}", f"/p:RuntimeLibrary={runtime_flag}", f"/p:UseMultiToolTask=true", f"/p:BuildInParallel=true", f"/m:{config.jobs}"] self.bus.work_mode("indeterminate") self.bus.work_control("start") def on_line_msbuild(line: str): self.bus.log(line, C.LogLevel.CMD) if ".cpp" in line or ".asm" in line: self.bus.work_text(Path(line.split(' ')[0]).name) build_ok = self._run_command_in_vs_env(build_args, source_dir, f"cryptopp_build_{build_type.value.lower()}", arch, stop_event, on_line=on_line_msbuild) self.bus.work_control("stop") self.bus.work_mode("determinate") self.bus.progress("work", 100 if build_ok else 0, "Tamamlandı" if build_ok else "Hata") if not build_ok: return False self.bus.log("Derleme sonrası kopyalama işlemi...", C.LogLevel.STAGE) try: output_dir = source_dir / ("x64" if arch == C.Arch.X64 else "Win32") / "Output" / build_type.value install_dir.mkdir(parents=True, exist_ok=True) (install_dir / "lib").mkdir(exist_ok=True); (install_dir / "include").mkdir(exist_ok=True) for f in output_dir.glob("*.lib"): shutil.copy2(f, install_dir / "lib") for header in source_dir.glob('*.h'): shutil.copy2(header, install_dir / "include") except Exception as e: self.bus.log(f"Crypto++ dosyaları kopyalanırken hata: {e}", C.LogLevel.ERR); return False return True def _build_lzo(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -> bool: for build_type in config.build_configs: if stop_event.is_set(): return False self.bus.log(f"--- LZO ({arch.value} / {build_type.value} / {config.runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE) build_dir = config.build_dir_for(arch, build_type) install_dir = config.install_dir_for(arch, build_type) cmake_exe = self.app.tool_manager.which("cmake", config.tool_mode == C.ToolMode.BUNDLED) if not cmake_exe: return False cmake_args = [ f'"{cmake_exe}"', "-S", f'"{source_dir}"', "-B", f'"{build_dir}"', "-G", "Ninja", f"-DCMAKE_INSTALL_PREFIX={install_dir}", f"-DCMAKE_BUILD_TYPE={build_type.value}", f"-DBUILD_SHARED_LIBS={'ON' if config.extra_opts.get('shared') else 'OFF'}", f"-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded{'Debug' if build_type == C.BuildType.DEBUG else ''}{'DLL' if config.runtime == C.Runtime.MD else ''}" ] if not self._run_command_in_vs_env(cmake_args, source_dir, f"lzo_config_{build_type.value.lower()}", arch, stop_event): return False build_args = [f'"{cmake_exe}"', "--build", f'"{build_dir}"', "--config", build_type.value, "--target", "install", "--parallel", str(config.jobs)] if not self._run_command_in_vs_env(build_args, source_dir, f"lzo_build_{build_type.value.lower()}", arch, stop_event, on_line=lambda s: self.app.ui_manager.update_build_stats(s, 'build_task')): return False return True def _build_spdlog(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -> bool: for build_type in config.build_configs: if stop_event.is_set(): return False self.bus.log(f"--- spdlog ({arch.value} / {build_type.value} / {config.runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE) build_dir = config.build_dir_for(arch, build_type) install_dir = config.install_dir_for(arch, build_type) cmake_exe = self.app.tool_manager.which("cmake", config.tool_mode == C.ToolMode.BUNDLED) if not cmake_exe: return False cmake_args = [ f'"{cmake_exe}"', "-S", f'"{source_dir}"', "-B", f'"{build_dir}"', "-G", "Ninja", f"-DCMAKE_INSTALL_PREFIX={install_dir}", f"-DCMAKE_BUILD_TYPE={build_type.value}", f"-DSPDLOG_BUILD_STATIC=ON", f"-DSPDLOG_BUILD_SHARED=OFF", f"-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded{'Debug' if build_type == C.BuildType.DEBUG else ''}{'DLL' if config.runtime == C.Runtime.MD else ''}", "-DSPDLOG_BUILD_EXAMPLES=OFF", "-DSPDLOG_BUILD_TESTS=OFF", "-DSPDLOG_BUILD_BENCH=OFF" ] if not self._run_command_in_vs_env(cmake_args, source_dir, f"spdlog_config_{build_type.value.lower()}", arch, stop_event): return False build_args = [f'"{cmake_exe}"', "--build", f'"{build_dir}"', "--config", build_type.value, "--target", "install", "--parallel", str(config.jobs)] if not self._run_command_in_vs_env(build_args, source_dir, f"spdlog_build_{build_type.value.lower()}", arch, stop_event, on_line=lambda s: self.app.ui_manager.update_build_stats(s, 'build_task')): return False return True def _build_nlohmann_json(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -> bool: for build_type in config.build_configs: if stop_event.is_set(): return False self.bus.log(f"--- nlohmann/json ({arch.value} / {build_type.value} / {config.runtime.value}) Kurulumu Başlıyor ---", C.LogLevel.STAGE) install_dir = config.install_dir_for(arch, build_type) try: src_include_path = source_dir / "include" if not src_include_path.exists(): self.bus.log(f"Kaynak 'include' klasörü bulunamadı: {src_include_path}", C.LogLevel.ERR) return False dest_include_path = install_dir / "include" self.bus.log(f"'{src_include_path}' kopyalanıyor -> '{dest_include_path}'", C.LogLevel.CMD) if dest_include_path.exists(): shutil.rmtree(dest_include_path) shutil.copytree(src_include_path, dest_include_path) self.bus.log("nlohmann/json başlık dosyaları başarıyla kopyalandı.", C.LogLevel.INFO) except Exception as e: self.bus.log(f"nlohmann/json dosyaları kopyalanırken hata: {e}", C.LogLevel.ERR) return False return True def _build_python(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -> bool: vs_env = self.app.tool_manager.vs_env_path() if not vs_env: return False msbuild_exe = Path(vs_env).parent.parent.parent.parent / "MSBuild/Current/Bin/MSBuild.exe" if not msbuild_exe.exists(): self.bus.log(f"MSBuild.exe bulunamadı: {msbuild_exe}", C.LogLevel.ERR); return False sln_file = source_dir / "PCbuild" / "pcbuild.sln" if not sln_file.exists(): self.bus.log(f"Python solution dosyası bulunamadı: {sln_file}", C.LogLevel.ERR); return False for build_type in config.build_configs: if stop_event.is_set(): return False self.bus.log(f"--- Python ({arch.value} / {build_type.value} / {config.runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE) install_dir = config.install_dir_for(arch, build_type) runtime_flag = "MultiThreaded" if config.runtime == C.Runtime.MT else "MultiThreadedDLL" if build_type == C.BuildType.DEBUG: runtime_flag += "Debug" platform = "x64" if arch == C.Arch.X64 else "Win32" targets = [] if config.extra_opts.get('shared'): targets.append("pythoncore") if config.extra_opts.get('static'): targets.append("pythoncore_static") if not targets: self.bus.log("Python için 'static' veya 'shared' seçilmedi, bu konfigürasyon atlanıyor.", C.LogLevel.WARN) continue # CPython'un kendi build sistemi, çıktıları $(SolutionDir)$(Platform)\$(Configuration) içine koyar output_dir = source_dir / "PCbuild" / platform / build_type.value build_args = [f'"{msbuild_exe}"', f'"{sln_file}"', f"/t:{';'.join(targets)}", f"/p:Configuration={build_type.value}", f"/p:Platform={platform}", f"/p:RuntimeLibrary={runtime_flag}", f"/p:UseMultiToolTask=true", f"/p:BuildInParallel=true", f"/m:{config.jobs}", f"/p:OutDir={output_dir}\\"] self.bus.work_mode("indeterminate") self.bus.work_control("start") def on_line_msbuild(line: str): self.bus.log(line, C.LogLevel.CMD) if ".cpp" in line or ".c" in line: self.bus.work_text(Path(line.split(' ')[0]).name) build_ok = self._run_command_in_vs_env(build_args, source_dir / "PCbuild", f"python_build_{build_type.value.lower()}", arch, stop_event, on_line=on_line_msbuild) self.bus.work_control("stop") self.bus.work_mode("determinate") self.bus.progress("work", 100 if build_ok else 0, "Tamamlandı" if build_ok else "Hata") if not build_ok: return False self.bus.log("Derleme sonrası kopyalama işlemi...", C.LogLevel.STAGE) try: lib_dir = (install_dir / "lib"); lib_dir.mkdir(parents=True, exist_ok=True) inc_dir = (install_dir / "include"); inc_dir.mkdir(parents=True, exist_ok=True) dll_dir = (install_dir / "bin"); dll_dir.mkdir(parents=True, exist_ok=True) self.bus.log("Kopyalanıyor: Başlık Dosyaları (Include, PC)", C.LogLevel.CMD) # Dirs_exist_ok=True, var olan bir hedefe kopyalamaya izin verir shutil.copytree(source_dir / "Include", inc_dir / "Include", dirs_exist_ok=True) shutil.copytree(source_dir / "PC", inc_dir / "PC", dirs_exist_ok=True) if config.extra_opts.get('shared'): self.bus.log("Kopyalanıyor: Paylaşımlı Kütüphane (.lib, .dll)", C.LogLevel.CMD) for f in output_dir.glob("python*.lib"): shutil.copy2(f, lib_dir) for f in output_dir.glob("python*.dll"): shutil.copy2(f, dll_dir) if config.extra_opts.get('static'): self.bus.log("Kopyalanıyor: Statik Kütüphane (_s.lib)", C.LogLevel.CMD) for f in output_dir.glob("python*_s.lib"): shutil.copy2(f, lib_dir) except Exception as e: self.bus.log(f"Python dosyaları kopyalanırken hata: {e}", C.LogLevel.ERR); return False return True 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("Lib Builder", "Derleme işlemi başarıyla tamamlandı.") else: self.bus.status("Derleme bir veya daha fazla hatayla tamamlandı.") messagebox.showwarning("Derleme Başarısız", "Derleme görevi başarısız oldu. Detaylar için logları inceleyin.") def _run_preflight_checks(self, config: BuildConfig) -> bool: self.bus.log("--- Uçuş Öncesi Kontroller Başlatıldı ---", C.LogLevel.STAGE) if not self.app.tool_manager.vs_env_path(): messagebox.showerror("Ortam Hatası", "Visual Studio 'Desktop development with C++' bileşeni kurulu değil."); return False required = ["cmake", "ninja"] if config.lib_key == "libjpeg-turbo": required.append("nasm") missing = [] for tool_key in required: meta = next((m for m in self.app.tool_manager.TOOLS_META if m["key"] == tool_key), None) if not meta or not self.app.tool_manager.detect_tool(meta, config.tool_mode)[0]: missing.append(meta['name'] if meta else tool_key) if missing: msg = f"Eksik araçlar: {', '.join(missing)}. Lütfen 'Araç Durumu' sekmesinden kurun." messagebox.showwarning("Eksik Araçlar", msg); 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: pass self.bus.log("--- Uçuş Öncesi Kontroller Başarıyla Tamamlandı ---", C.LogLevel.STAGE) return True def clear_build_folder(self) -> None: if self.app._is_busy: return if not C.BUILD.exists() or not any(C.BUILD.iterdir()): 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(); messagebox.showinfo("Başarılı", "'build' klasörü temizlendi.") except OSError as e: messagebox.showerror("Hata", f"Silinemedi:\n{e}") def fetch_tags_async(self, lib_key: str, combobox: ttk.Combobox) -> None: if self.app._is_busy: return meta = C.LIB_META[lib_key] self.app.task_manager.register(name=f"{meta['name']} Sürüm Listeleme", target=self._fetch_tags_worker, args=(meta, combobox), is_build_task=False) def _fetch_tags_worker(self, meta: dict, combobox: ttk.Combobox, task_id: str) -> None: self.bus.log(f"{meta['name']} GitHub etiketleri çekiliyor...", C.LogLevel.INFO) tags = [] try: git = self.app.tool_manager.which("git", False) if git: out = run_out([git, "ls-remote", "--tags", meta['repo']]) versions = [] for ln in out.splitlines(): m = re.search(r"refs/tags/(" + re.escape(meta['tag_prefix']) + r".*)$", ln) if m and meta['tag_filter'].match(m.group(1)): versions.append(m.group(1)) tags = sorted(versions, key=lambda v: list(map(int, re.findall(r'\d+', v))), reverse=True)[:50] except Exception as e: self.bus.log(f"Git ile etiket çekme hatası: {e}", C.LogLevel.WARN) if not tags: messagebox.showerror("Hata", "Sürümler listelenemedi. İnternet bağlantınızı veya Git kurulumunu kontrol edin.") lib_key_from_meta = next(key for key, m in C.LIB_META.items() if m['name'] == meta['name']) last_tag = self.app.ui_manager.lib_tabs.get(lib_key_from_meta, {}).get('last_tag_from_config') selected_tag = (last_tag if last_tag in tags else tags[0]) if tags else "" self.app.after(0, lambda: (combobox.config(values=tags), combobox.set(selected_tag))) self.app.task_manager.task_registry[task_id]['success'] = True def _fetch_source(self, lib_key: str, tag: str, stop_event: threading.Event) -> Path | None: meta = C.LIB_META[lib_key] dst = C.SRC / lib_key / tag if stop_event.is_set(): return None if dst.exists() and any(dst.iterdir()): self.bus.log("Kaynaklar zaten çıkarılmış, atlandı.", C.LogLevel.STAGE); return dst if meta.get('type') == 'url': url = meta['url'] archive_name = Path(url).name zip_path = C.DL / archive_name.replace(":", "_") if not zip_path.exists(): if not self.app.tool_manager._http_get(url, zip_path, self.bus): raise RuntimeError("Kaynak arşivi indirilemedi.") else: # git type zip_name = tag.replace('/', '_') zip_path = C.DL / f"{lib_key}-{zip_name}.zip" if not zip_path.exists(): url = f"{meta['repo']}/archive/refs/tags/{tag}.zip" if not self.app.tool_manager._http_get(url, zip_path, self.bus): raise RuntimeError("Kaynak ZIP alınamadı.") if stop_event.is_set(): return None extract_dir = C.SRC / lib_key / "_tmp" if extract_dir.exists(): shutil.rmtree(extract_dir) extract_dir.mkdir(parents=True) try: self.bus.log(f"'{zip_path.name}' arşivi çıkarılıyor...", C.LogLevel.STAGE) shutil.unpack_archive(zip_path, extract_dir) self.bus.log("Arşiv başarıyla çıkarıldı.", C.LogLevel.INFO) except Exception as e: self.bus.log(f"Arşiv çıkarılırken hata oluştu: {e}", C.LogLevel.ERR) self.bus.log(traceback.format_exc(), C.LogLevel.ERR) raise RuntimeError("Arşiv dosyası çıkarılamadı.") items_in_extract_dir = list(extract_dir.iterdir()) if len(items_in_extract_dir) == 1 and items_in_extract_dir[0].is_dir(): self.bus.log("Tek bir kök dizin algılandı, taşınıyor...", C.LogLevel.INFO) source_root = items_in_extract_dir[0] if dst.exists(): shutil.rmtree(dst) shutil.move(str(source_root), str(dst)) shutil.rmtree(extract_dir) else: self.bus.log("Kök dizin bulunamadı, geçici klasörün kendisi kaynak olarak kullanılıyor.", C.LogLevel.INFO) if dst.exists(): shutil.rmtree(dst) shutil.move(str(extract_dir), str(dst)) if not dst.exists(): raise RuntimeError("Kaynak çıkarma sonrası klasör bulunamadı.") return dst def initial_trash_cleanup(self) -> None: if C.TRASH.exists() and 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)); p.mkdir(exist_ok=True) except OSError as e: self.bus.log(f"'{p}' taşınamadı: {e}.", C.LogLevel.ERR); raise 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. Devam etmek istiyor musunuz?"): 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="Lib Builder Pro v7.4 (Final) - turkmmo.com - dormammu", size=(1320, 960), 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) for lib_key, widgets in self.ui_manager.lib_tabs.items(): if 'cmb_tag' in widgets: self.after(200, lambda k=lib_key, c=widgets['cmb_tag']: self.build_manager.fetch_tags_async(k, c)) 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.protocol("WM_DELETE_WINDOW", self.on_closing) def on_closing(self) -> None: if self._is_busy and 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() def set_busy(self, busy: bool) -> None: self._is_busy = busy self.ui_manager.set_controls_state('disabled' if busy else 'normal') try: priority = psutil.IDLE_PRIORITY_CLASS if busy else psutil.NORMAL_PRIORITY_CLASS self.process.nice(priority) except psutil.Error: pass if __name__ == "__main__": for p in (C.LOGS, C.TOOLS, C.DL, C.SRC, C.BUILD, C.INST, C.DEPS, C.TRASH, C.INST_LIBJPEG_TURBO, C.INST_DEVIL, C.INST_CRYPTOPP, C.INST_LZO, C.INST_SPDLOG, C.INST_NLOHMANN_JSON, C.INST_PYTHON): p.mkdir(parents=True, exist_ok=True) app = App() app.mainloop()
🛠️ Program Nasıl Kullanılır? 🛠️
- <li data-xf-list-type="ol">Araçları Kur: İlk olarak, "Araç Durumu" panelindeki eksikleri "Eksikleri Kur" butonuyla tamamlayın. <li data-xf-list-type="ol">Ayarları Yap:
- <li data-xf-list-type="ul">"Ortak Ayarlar" kısmından Metin2 client için zorunlu olan x86 (32-bit) mimarisinin seçili olduğundan emin olun. <li data-xf-list-type="ul">Runtime olarak genellikle MT seçilir. <li data-xf-list-type="ul">Her kütüphane sekmesine gidip "Listele" butonuna basarak en güncel sürümü seçin (veya istediğiniz başka bir sürümü).
Tüm soru, sorun ve önerilerinizi konu altından bekliyorum.
Saygılarımla,dormammu
Lib Builder Pro: Otomatik Kütüphane Derleme Aracı
Metin2 geliştiricileri için önemli bir araçtan bahsetmek istiyoruz; Lib Builder Pro. Bu araç, libjpeg, DevIL, Crypto++ gibi popüler kütüphaneleri otomatik olarak derlemek ve projelerinize entegre etmek için tasarlanmıştır. Özellikle C++ tabanlı Metin2 sunucu kaynak kodları üzerinde çalışan geliştiriciler için büyük kolaylık sağlar.
Lib Builder Pro Nedir?
Lib Builder Pro, geliştiricilerin zaman kazanmasını sağlayan otomatik derleme çözümlerinden biridir. Yazılım projelerinde kullanılan harici kütüphaneleri derlemek genellikle uzun süren ve hataya açık bir işlemdir. Bu aracın temel amacı, bu işlemi hızlandırmak ve otomatikleştirmektir. Özellikle Metin2 server src[/COORD] geliştirenler için bu, büyük bir avantajdır.
Desteklenen Kütüphaneler
Aracın desteklediği başlıca kütüphaneler şunlardır:
- libjpeg: JPEG görüntü işleme kütüphanesi
- DevIL: Geniş platform desteği sunan görüntü işleme kütüphanesi
- Crypto++: Şifreleme ve güvenlik odaklı kriptografi kütüphanesi
Bu kütüphaneler, özellikle game core ve client src geliştirme süreçlerinde sıklıkla kullanılır. Lib Builder Pro sayesinde bu kütüphaneleri derlemek için manuel adımlar atmak yerine, tek bir komutla süreci tamamlayabilirsiniz.
Neden Lib Builder Pro Kullanmalısınız?
Otomatik Derleme: Manuel derleme işlemlerinde oluşabilecek hataları ortadan kaldırır.
Zaman Tasarrufu: Geliştirme sürecini hızlandırır.
Kolay Entegrasyon: Hazırlanan kütüphaneler doğrudan projenize dahil edilebilir.
Platform Uyumluluğu: Windows, Linux ve diğer platformlarda çalışabilir.
Metin2 Geliştirme Sürecine Entegrasyonu
Lib Builder Pro, Metin2 development sürecinde önemli bir yer tutar. Özellikle source edit veya server src üzerinde çalışırken, harici bağımlılıkların doğru şekilde derlenmiş olması gerekir. Bu araç sayesinde, örneğin Crypto++ kütüphanesini kullanarak güçlü güvenlik katmanları oluşturabilirsiniz. Aynı zamanda libjpeg sayesinde görsel içeriklerinizi optimize edebilirsiniz.
Lib Builder Pro ile Metin2 PVP Sistemleri Geliştirme
Metin2 PVP sistem geliştirme sürecinde, bazı güvenlik ve performans kütüphaneleri mutlaka gereklidir. Lib Builder Pro, bu kütüphaneleri hızlıca derleyip projeye entegre etmenizi sağlar. Böylece game server programming sırasında daha güvenli ve optimize çözümler elde edebilirsiniz. Örneğin, DevIL ile sunucu tarafında dinamik olarak resim yüklemeleri yapabilirsiniz.
Sonuç
Lib Builder Pro, Metin2 özel sunucuları geliştiren herkes için oldukça değerli bir araçtır. Özellikle C++ sistem odaklı çalışmalarda, derleme sürecini büyük ölçüde kolaylaştırır. Metin2Lobby olarak, bu tarz gelişmiş araçların geliştiriciler tarafından kullanılmasını teşvik ediyoruz. Lib Builder Pro ile pack dosyalarınıza daha hızlı ve güvenli bir şekilde erişim sağlayabilir, auth ve db süreçlerini optimize edebilirsiniz.
Lib Builder Pro: Automatic Library Building Tool
We would like to discuss an important tool for Metin2 developers; Lib Builder Pro. This tool is designed to automatically build popular libraries such as libjpeg, DevIL, Crypto++ and integrate them into your projects. It provides great convenience especially for developers working on C++ based Metin2 server sources.
What is Lib Builder Pro?
Lib Builder Pro is one of the automated building solutions that help developers save time. Compiling external libraries used in software projects is often a lengthy and error-prone process. The main purpose of this tool is to speed up and automate this process. Especially for those developing Metin2 server src, this is a major advantage.
Supported Libraries
The main libraries supported by the tool are:
- libjpeg: JPEG image processing library
- DevIL: Cross-platform supporting image processing library
- Crypto++: Cryptography library focused on encryption and security
These libraries are frequently used during game core and client src development processes. With Lib Builder Pro, you can complete the process with a single command instead of manually taking steps to build these libraries.
Why Should You Use Lib Builder Pro?
Automatic Building: Eliminates potential errors in manual building processes.
Time Saving: Accelerates the development process.
Easy Integration: Built libraries can be directly included in your project.
Platform Compatibility: Works on Windows, Linux, and other platforms.
Integration into Metin2 Development Process
Lib Builder Pro holds a significant place in the Metin2 development process. Especially when working on source edit or server src, it's essential that external dependencies are correctly built. With this tool, you can, for example, create robust security layers using the Crypto++ library. Additionally, with libjpeg, you can optimize your visual content.
Developing Metin2 PVP Systems with Lib Builder Pro
In the process of developing Metin2 PVP systems, certain security and performance libraries are essential. Lib Builder Pro allows you to quickly build and integrate these libraries into your project. This way, during game server programming, you can achieve more secure and optimized solutions. For instance, with DevIL, you can dynamically load images on the server side.
Conclusion
Lib Builder Pro is an invaluable tool for anyone developing Metin2 private servers. Especially in C++ system focused work, it significantly simplifies the building process. At Metin2Lobby, we encourage developers to make use of such advanced tools. With Lib Builder Pro, you can access your pack files faster and more securely, and optimize auth and db processes.
