From 8283a6d5005696f396af28c0ed2e11de0d9da4e4 Mon Sep 17 00:00:00 2001 From: OusmBlueNinja <89956790+OusmBlueNinja@users.noreply.github.com> Date: Sun, 13 Apr 2025 13:17:59 -0500 Subject: [PATCH] make remake --- remake.py | 0 remake/remake.py | 233 ++++++++++++++++++++++++++++++++++++++++ remake/remake_config.py | 34 ++++++ 3 files changed, 267 insertions(+) delete mode 100644 remake.py create mode 100644 remake/remake.py create mode 100644 remake/remake_config.py diff --git a/remake.py b/remake.py deleted file mode 100644 index e69de29..0000000 diff --git a/remake/remake.py b/remake/remake.py new file mode 100644 index 0000000..cbb1665 --- /dev/null +++ b/remake/remake.py @@ -0,0 +1,233 @@ +import os +import subprocess +from pathlib import Path +import sys +import shutil +import time +import json +from remake_config import * + +# ========== COLOR UTILS ========== +class Colors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + BOLD = '\033[1m' + RESET = '\033[0m' + GRAY = '\033[90m' + +def color(text, style): return f"{style}{text}{Colors.RESET}" +def banner(title): print(color(f"\n╔═ {title} ═══════════════════════════════╗", Colors.BOLD + Colors.OKBLUE)) +def info(msg): print(color(f"✅ {msg}", Colors.OKGREEN)) +def warn(msg): print(color(f"⚠️ {msg}", Colors.WARNING)) +def error(msg): print(color(f"❌ {msg}", Colors.FAIL)) + +def log(msg: str): + with LOG_FILE.open("a", encoding="utf-8") as f: + f.write(msg + "\n") + +# ========== CACHE ========== +def load_cache(): + try: + if CACHE_FILE.exists(): + return json.loads(CACHE_FILE.read_text()) + except json.decoder.JSONDecodeError: + error("Failed to Read Cache File.") + return {} + +def save_cache(data): + CACHE_FILE.write_text(json.dumps(data, indent=2)) + +# ========== PACKAGE DISCOVERY ========== +class AutoLib: + def __init__(self, name): + self.name = name + self.path = None + + def find(self, search_paths, cache): + if self.name in cache: + self.path = Path(cache[self.name]) + return self.path.exists() + + for path in search_paths: + for root, _, files in os.walk(path): + for ext in [".lib", ".a"]: + fname = f"lib{self.name}{ext}" + if fname in files: + self.path = Path(root) / fname + cache[self.name] = str(self.path) + info(f"Found {self.name} at {self.path}") + return True + return False + +class AutoInclude: + def __init__(self, name): + self.name = name + self.path = None + + def find(self, search_paths, cache): + if self.name in cache: + self.path = Path(cache[self.name]) + return self.path.exists() + + for path in search_paths: + for root, _, files in os.walk(path): + if f"{self.name}.h" in files or Path(root).name == self.name: + self.path = Path(root) + cache[self.name] = str(self.path) + info(f"Found header {self.name} at {self.path}") + return True + return False + +def resolve_packages(): + cache = load_cache() + extra_link_paths, resolved_libs, extra_includes = [], [], [] + + for name in AUTO_LIBS: + lib = AutoLib(name) + if lib.find(LIB_DIRS, cache): + extra_link_paths.append(str(lib.path.parent)) + resolved_libs.append(f"-l{lib.name}") + else: + error(f"Library {lib.name} not found.") + sys.exit(1) + + for name in AUTO_INCLUDES: + inc = AutoInclude(name) + if inc.find(INCLUDE_DIRS, cache): + extra_includes.append(str(inc.path)) + + save_cache(cache) + return list(set(extra_link_paths)), resolved_libs, list(set(extra_includes)) + +# ========== BUILD SYSTEM ========== +def find_cpp_files(): + cpp_files = [] + for folder in SRC_DIRS: + for path in Path(folder).rglob("*.cpp"): + cpp_files.append(path) + return cpp_files + +def obj_path(source): return BUILD_DIR / source.relative_to("src").with_suffix(".o") +def dep_path(obj): return obj.with_suffix(".d") + +def parse_dep_file(dep_file): + if not dep_file.exists(): return [] + deps = [] + with dep_file.open() as f: + for line in f: + line = line.strip().replace("\\", "") + if ":" in line: + line = line.split(":", 1)[1] + deps.extend(line.strip().split()) + return deps + +def compile_source(source, obj, includes): + obj.parent.mkdir(parents=True, exist_ok=True) + cmd = [CXX, *CXXFLAGS, *[f"-I{inc}" for inc in includes], "-MMD", "-MP", "-c", str(source), "-o", str(obj)] + try: + print(f"{color('🔨 Compiling:', Colors.OKCYAN)} {source}") + subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + log(f"[COMPILE] {' '.join(cmd)}") + except subprocess.CalledProcessError as e: + error(f"Failed to compile {source}") + print("🔧 Command:", " ".join(cmd)) + print(e.stderr.decode()) + log(f"[ERROR] {' '.join(cmd)}\n{e.stderr.decode()}") + sys.exit(1) + +def link_objects(obj_files, link_dirs, libs): + cmd = [CXX, *map(str, obj_files), "-o", str(TARGET), *[f"-L{p}" for p in link_dirs], *libs] + try: + print(f"{color('📦 Linking:', Colors.OKBLUE)} {TARGET}") + subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + log(f"[LINK] {' '.join(cmd)}") + except subprocess.CalledProcessError as e: + error("Linking failed.") + print("🔧 Command:", " ".join(cmd)) + print(e.stderr.decode()) + log(f"[ERROR] {' '.join(cmd)}\n{e.stderr.decode()}") + sys.exit(1) + +def build(): + build_start = time.time() + banner("🚀 Building Project") + cpp_files = find_cpp_files() + obj_files = [] + + link_dirs, libs, extra_includes = resolve_packages() + all_includes = INCLUDE_DIRS + extra_includes + + for source in cpp_files: + obj = obj_path(source) + dep = dep_path(obj) + obj_mtime = obj.stat().st_mtime if obj.exists() else 0 + needs_build = not obj.exists() or source.stat().st_mtime > obj_mtime + + if not needs_build and dep.exists(): + for dep_file in parse_dep_file(dep): + try: + if Path(dep_file).exists() and Path(dep_file).stat().st_mtime > obj_mtime: + needs_build = True + break + except Exception: + needs_build = True + break + + if needs_build: + compile_source(source, obj, all_includes) + else: + print(f"{color('👌 Up-to-date:', Colors.GRAY)} {source}") + obj_files.append(obj) + + link_objects(obj_files, link_dirs, libs) + banner("✅ Build Complete") + print(color(f"⏱ Build time: {time.time() - build_start:.2f}s", Colors.OKCYAN)) + +def run(): + build() + if TARGET.exists(): + banner("🚀 Running") + try: + subprocess.run(str(TARGET), check=True) + log("[RUN] Executed app.exe successfully.") + except subprocess.CalledProcessError as e: + error("Program exited with error.") + log(f"[ERROR] Runtime crash\n{e}") + sys.exit(e.returncode) + else: + error("Executable not found.") + +def clean(): + banner("🧹 Cleaning") + if BUILD_DIR.exists(): + shutil.rmtree(BUILD_DIR) + info("Build directory removed.") + if LOG_FILE.exists(): + LOG_FILE.unlink() + info("Log file cleared.") + if CACHE_FILE.exists(): + CACHE_FILE.unlink() + info("Cache file cleared.") + +# ========== ENTRY ========== +if __name__ == "__main__": + start = time.time() + LOG_FILE.write_text("", encoding="utf-8") + + try: + if "clean" in sys.argv: + clean() + elif "run" in sys.argv: + run() + else: + build() + except KeyboardInterrupt: + error("Interrupted by user.") + log("[ERROR] Interrupted by user.") + sys.exit(1) + + print(color(f"\n⏱ Total time: {time.time() - start:.2f}s", Colors.BOLD + Colors.OKGREEN)) \ No newline at end of file diff --git a/remake/remake_config.py b/remake/remake_config.py new file mode 100644 index 0000000..4837fa3 --- /dev/null +++ b/remake/remake_config.py @@ -0,0 +1,34 @@ +from pathlib import Path + +# Source and header directories +SRC_DIRS = ["src/src", "src/vendor"] +INCLUDE_DIRS = [ + "src/include", + "src/vendor", + "src/vendor/imgui", + "C:/msys64/mingw64/include" +] + +# Default library search paths +LIB_DIRS = [ + "C:/msys64/mingw64/lib", + "C:/Program Files", + "C:/Program Files (x86)", + "C:/libs", +] + +# Compiler and build options +BUILD_DIR = Path("src/build") +TARGET = BUILD_DIR / "app.exe" +LOG_FILE = Path("build.log") +CACHE_FILE = Path("./remake/.remake_cache.json") +CXX = "g++" +CXXFLAGS = ["-std=c++20", "-Wall"] + [f"-I{inc}" for inc in INCLUDE_DIRS] + +# Libraries and includes to auto-discover +AUTO_LIBS = [ + "glfw3", "glew32", "opengl32", "gdi32", + "yaml-cpp", "comdlg32", "ssl", "crypto" +] + +AUTO_INCLUDES = ["imgui", "yaml-cpp"]