218 lines
6.1 KiB
Python
218 lines
6.1 KiB
Python
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
import sys
|
|
import shutil
|
|
import time
|
|
|
|
# ========== CONFIG ==========
|
|
SRC_DIRS = ["src/src", "src/vendor"]
|
|
|
|
|
|
INCLUDE_DIRS = [
|
|
"src/include",
|
|
"src/vendor",
|
|
"src/vendor/imgui",
|
|
"C:/msys64/mingw64/include"
|
|
]
|
|
|
|
|
|
|
|
LIB_DIRS = ["C:/msys64/mingw64/lib"]
|
|
BUILD_DIR = Path("src/build")
|
|
TARGET = BUILD_DIR / "app.exe"
|
|
LOG_FILE = Path("build.log")
|
|
LIBS = ["glfw3", "glew32", "opengl32", "gdi32", "yaml-cpp", "comdlg32", "ssl", "crypto"]
|
|
|
|
|
|
CXX = "g++"
|
|
CXXFLAGS = ["-std=c++20", "-Wall"] + [f"-I{inc}" for inc in INCLUDE_DIRS]
|
|
LDFLAGS = [f"-L{lib}" for lib in LIB_DIRS] + [f"-l{lib}" for lib in LIBS]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 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")
|
|
|
|
# ========== 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):
|
|
obj.parent.mkdir(parents=True, exist_ok=True)
|
|
cmd = [CXX, *CXXFLAGS, "-MMD", "-MP", "-c", str(source), "-o", str(obj)]
|
|
try:
|
|
print(f"{color('🔨 Compiling:', Colors.OKCYAN)} {source}")
|
|
result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
log(f"[COMPILE] {' '.join(cmd)}{result.stdout.decode()}{result.stderr.decode()}")
|
|
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):
|
|
cmd = [CXX, *map(str, obj_files), "-o", str(TARGET), *LDFLAGS]
|
|
try:
|
|
print(f"{color('📦 Linking:', Colors.OKBLUE)} {TARGET}")
|
|
result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
log(f"[LINK] {' '.join(cmd)}\n{result.stdout.decode()}{result.stderr.decode()}")
|
|
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 = []
|
|
|
|
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()
|
|
|
|
# If source is newer than object
|
|
if not needs_build and source.stat().st_mtime > obj_mtime:
|
|
needs_build = True
|
|
|
|
# If any dependencies are newer than object
|
|
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)
|
|
else:
|
|
print(f"{color('👌 Up-to-date:', Colors.GRAY)} {source}")
|
|
obj_files.append(obj)
|
|
|
|
link_objects(obj_files)
|
|
banner("✅ Build Complete")
|
|
build_end = time.time()
|
|
log(f"[TIME] Build duration: {build_end - build_start:.2f}s")
|
|
print(color(f"⏱ Build time: {build_end - build_start:.2f}s", Colors.OKCYAN))
|
|
|
|
|
|
|
|
|
|
|
|
def run():
|
|
build()
|
|
if TARGET.exists():
|
|
banner("🚀 Running")
|
|
try:
|
|
result = 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.")
|
|
log("[ERROR] Executable not found.")
|
|
|
|
def clean():
|
|
banner("🧹 Cleaning")
|
|
if BUILD_DIR.exists():
|
|
shutil.rmtree(BUILD_DIR)
|
|
info("Build directory removed.")
|
|
log("[CLEAN] Build directory removed.")
|
|
else:
|
|
warn("Nothing to clean.")
|
|
log("[CLEAN] No build directory found.")
|
|
if LOG_FILE.exists():
|
|
LOG_FILE.unlink()
|
|
info("Log file cleared.")
|
|
|
|
# ========== ENTRY ==========
|
|
if __name__ == "__main__":
|
|
total_start = time.time()
|
|
|
|
# Clear old log
|
|
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] Build interrupted by user.")
|
|
sys.exit(1)
|
|
|
|
total_end = time.time()
|
|
print(color(f"\n⏱ Total time: {total_end - total_start:.2f}s", Colors.BOLD + Colors.OKGREEN))
|
|
log(f"[TIME] Total runtime: {total_end - total_start:.2f}s")
|