Created a python build script
This commit is contained in:
parent
08205ea6bc
commit
b0be7f0c0b
|
|
@ -0,0 +1,207 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# CONFIG
|
||||
# ---------------------------------------------------------------------
|
||||
SRC_DIR = Path("src")
|
||||
TEST_DIR = Path("tests")
|
||||
OBJ_DIR = Path("obj")
|
||||
BIN_DIR = Path("bin")
|
||||
|
||||
TARGET = BIN_DIR / "sls"
|
||||
TEST_TARGET = BIN_DIR / "sls_tests"
|
||||
|
||||
# Unix gcc/clang flags
|
||||
COMMON_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Werror", "-Iinclude", "-g"]
|
||||
TEST_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Wno-unused-function", "-Werror",
|
||||
"-Iinclude", "-g", "-O0"]
|
||||
|
||||
# Windows MSVC flags
|
||||
MSVC_FLAGS = ["/std:c11", "/Zi", "/Iinclude"]
|
||||
MSVC_TEST_FLAGS = MSVC_FLAGS + []
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# COMPILER DETECTION
|
||||
# ---------------------------------------------------------------------
|
||||
def detect_compiler():
|
||||
if os.name == "nt":
|
||||
return ("cl", "msvc")
|
||||
return ("gcc", "gcc")
|
||||
|
||||
CC, CC_KIND = detect_compiler()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# GIT COMMIT HASH
|
||||
# ---------------------------------------------------------------------
|
||||
def git_commit_hash():
|
||||
try:
|
||||
result = subprocess.check_output(
|
||||
["git", "describe", "--always", "--dirty", "--abbrev=7"],
|
||||
cwd=".",
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True
|
||||
)
|
||||
return result.strip()
|
||||
except Exception:
|
||||
return "unknown"
|
||||
|
||||
|
||||
GIT_HASH = git_commit_hash()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# UTILS
|
||||
# ---------------------------------------------------------------------
|
||||
def mkdir(p: Path):
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def run(cmd):
|
||||
print(">>", " ".join(cmd))
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def is_up_to_date(src, obj, dep):
|
||||
if not obj.exists():
|
||||
return False
|
||||
if dep.exists() and dep.stat().st_mtime > obj.stat().st_mtime:
|
||||
return False
|
||||
return src.stat().st_mtime < obj.stat().st_mtime
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# BUILD RULES
|
||||
# ---------------------------------------------------------------------
|
||||
def compile_source(src: Path, is_test=False):
|
||||
mkdir(OBJ_DIR)
|
||||
obj = OBJ_DIR / (src.stem + ".o")
|
||||
dep = OBJ_DIR / (src.stem + ".d")
|
||||
|
||||
if is_up_to_date(src, obj, dep):
|
||||
return obj
|
||||
|
||||
if CC_KIND == "msvc":
|
||||
flags = MSVC_TEST_FLAGS if is_test else MSVC_FLAGS
|
||||
cmd = [CC] + flags + ["/Fo" + str(obj), "/c", str(src),
|
||||
f"/DGIT_COMMIT_HASH=\"{GIT_HASH}\""]
|
||||
else:
|
||||
flags = TEST_FLAGS if is_test else COMMON_FLAGS
|
||||
cmd = [CC] + flags + [
|
||||
f"-DGIT_COMMIT_HASH=\"{GIT_HASH}\"",
|
||||
"-MMD", "-MP",
|
||||
"-c", str(src), "-o", str(obj)
|
||||
]
|
||||
|
||||
run(cmd)
|
||||
return obj
|
||||
|
||||
|
||||
def link_executable(objects, output: Path):
|
||||
mkdir(BIN_DIR)
|
||||
if CC_KIND == "msvc":
|
||||
cmd = [CC] + list(map(str, objects)) + ["/Fe" + str(output)]
|
||||
else:
|
||||
cmd = [CC] + list(map(str, objects)) + ["-lm", "-o", str(output)]
|
||||
run(cmd)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# TEST CASE GENERATION
|
||||
# ---------------------------------------------------------------------
|
||||
def generate_tests():
|
||||
script = Path("../SLS_Tests/yaml_to_c_tests.py")
|
||||
yaml = Path("../SLS_Tests/cases.yaml")
|
||||
out = TEST_DIR / "lexer_tests.c"
|
||||
|
||||
if not script.exists() or not yaml.exists():
|
||||
print("Test generation skipped (missing files).")
|
||||
return False
|
||||
|
||||
if out.exists() and out.stat().st_mtime > yaml.stat().st_mtime:
|
||||
return True
|
||||
|
||||
run(["python3", str(script), str(yaml), str(out)])
|
||||
return True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# TARGETS
|
||||
# ---------------------------------------------------------------------
|
||||
def build_main():
|
||||
sources = list(SRC_DIR.glob("*.c"))
|
||||
objects = [compile_source(s, is_test=False) for s in sources]
|
||||
link_executable(objects, TARGET)
|
||||
|
||||
|
||||
def build_tests():
|
||||
generate_tests()
|
||||
test_sources = list(TEST_DIR.glob("*.c"))
|
||||
main_sources = [s for s in SRC_DIR.glob("*.c") if s.name != "main.c"]
|
||||
|
||||
test_objects = [compile_source(s, is_test=True) for s in test_sources]
|
||||
shared_objects = [compile_source(s, is_test=False) for s in main_sources]
|
||||
|
||||
link_executable(test_objects + shared_objects, TEST_TARGET)
|
||||
|
||||
|
||||
def clean():
|
||||
shutil.rmtree(OBJ_DIR, ignore_errors=True)
|
||||
shutil.rmtree(BIN_DIR, ignore_errors=True)
|
||||
print("Cleaned.")
|
||||
|
||||
|
||||
def run_main():
|
||||
build_main()
|
||||
print("\n--- Running program ---\n")
|
||||
subprocess.call([str(TARGET)])
|
||||
|
||||
|
||||
def run_tests():
|
||||
build_tests()
|
||||
print("\n--- Running tests ---\n")
|
||||
subprocess.call([str(TEST_TARGET)])
|
||||
|
||||
|
||||
def debug_tests():
|
||||
build_tests()
|
||||
if os.name == "nt":
|
||||
print("Debugging on Windows requires Visual Studio debugger.")
|
||||
else:
|
||||
subprocess.call(["gdb", str(TEST_TARGET)])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# ENTRY POINT
|
||||
# ---------------------------------------------------------------------
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python3 build.py [all|build|test|run|debug|clean]")
|
||||
return
|
||||
|
||||
cmd = sys.argv[1]
|
||||
|
||||
match cmd:
|
||||
case "all" | "main":
|
||||
build_main()
|
||||
case "build":
|
||||
build_main()
|
||||
case "run":
|
||||
run_main()
|
||||
case "test":
|
||||
run_tests()
|
||||
case "debug":
|
||||
debug_tests()
|
||||
case "clean":
|
||||
clean()
|
||||
case _:
|
||||
print(f"Unknown target: {cmd}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue