make remake
This commit is contained in:
parent
5f4dae2bbd
commit
8283a6d500
233
remake/remake.py
Normal file
233
remake/remake.py
Normal file
@ -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))
|
34
remake/remake_config.py
Normal file
34
remake/remake_config.py
Normal file
@ -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"]
|
Loading…
Reference in New Issue
Block a user