- 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 2 Gün
- Başarım Puanı
- 129
- Yaş
- 32
- MmoLira
- 6,460
- DevLira
- 78
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!
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.
- 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.
- Metin2 Uyumlu: Client için zorunlu olan 32-bit (x86) mimarisini ve isteğe bağlı olarak 64-bit (x64) mimarisini destekler.
- 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.
- 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!
1. Visual Studio 2022 (C++ İş Yükü)
Bu adım olmadan program KESİNLİKLE ÇALIŞMAZ!
- Visual Studio 2022 kurulu olmalı.
- 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.
2. Python ve Gerekli Paketler
Bu araç Python ile yazılmıştır. Bu yüzden sisteminizde Python kurulu olmalıdır.
- Python Sürümü: Python 3.14 veya daha yenibir sürüm gereklidir.
- PowerShell'i açıp python --version komutuyla sürümünüzü kontrol edebilirsiniz.
- Kurulu değilse,
Linkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir.indirip kurarken "Add Python to PATH" seçeneğini işaretlemeyi UNUTMAYIN!
- Gerekli Python Paketleri: Programın çalışması için birkaç ek pakete ihtiyacı var. PowerShell'i açıp aşağıdaki komutu çalıştırarak hepsini tek seferde kurun:
[CODE title="Powershell"]pip install requests psutil ttkbootstrap[/CODE]
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.
- 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:
- 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.
- Açılan PowerShell ekranına aşağıdaki komutu yazın ve Enter'a basın:
Tebrikler! Programın arayüzü karşınıza gelecek.
Python:
# -*- 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?
- Araçları Kur: İlk olarak, "Araç Durumu" panelindeki eksikleri "Eksikleri Kur" butonuyla tamamlayın.
- Ayarları Yap:
- "Ortak Ayarlar" kısmından Metin2 client için zorunlu olan x86 (32-bit) mimarisinin seçili olduğundan emin olun.
- Runtime olarak genellikle MT seçilir.
- 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ü).
- Derlemeyi Başlat: Sağdaki "Tüm Kütüphaneleri Derle" butonuna basın. Bu en kolay yöntemdir, çünkü bağımlılık sırasını kendi ayarlar.
- Kullanıma Hazır: İşlem bitince "Install Klasörünü Aç" butonuna basın. Açılan klasörde, her kütüphane için ayrı ayrı klasörler göreceksiniz. Örneğin install\devil\x86\MT\Release klasörünün içindeki include ve lib klasörlerini, kendi source'unuzun extern klasöründeki ilgili yerlere kopyalayın.
Tüm soru, sorun ve önerilerinizi konu altından bekliyorum.
Saygılarımla,dormammu
Ekli dosyalar
Son düzenleme:
- Katılım
- 31 Eki 2022
- Konular
- 217
- Mesajlar
- 690
- Çözüm
- 1
- Online süresi
- 29d 10h
- Reaksiyon Skoru
- 666
- Altın Konu
- 18
- Başarım Puanı
- 171
- MmoLira
- 2,890
- DevLira
- 30
Paylaşım için teşekkürler, Elinize sağlık.
- Katılım
- 25 Haz 2024
- Konular
- 125
- Mesajlar
- 839
- Çözüm
- 16
- Online süresi
- 2mo 11d
- Reaksiyon Skoru
- 505
- Altın Konu
- 1
- Başarım Puanı
- 164
- MmoLira
- 3,691
- DevLira
- 45
Eline sağlık çok iyi olmuş çok fazla problem yaşayan vardı bu konuda.
Şu an konuyu görüntüleyenler (Toplam : 0, Üye: 0, Misafir: 0)
Benzer konular
- Cevaplar
- 2
- Görüntüleme
- 266
- Cevaplar
- 13
- Görüntüleme
- 1K
- Cevaplar
- 6
- Görüntüleme
- 1K
- Cevaplar
- 8
- Görüntüleme
- 385
- Cevaplar
- 6
- Görüntüleme
- 277







