2026-08-27 19:08:08 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""generates a Ninja build file
|
|
|
|
|
|
|
|
|
|
usage:
|
|
|
|
|
python3 configure.py
|
|
|
|
|
python3 configure.py --asan # enable address/undefined sanitizers
|
|
|
|
|
python3 configure.py --debug # -O0 instead of -O2
|
2026-08-27 22:50:44 +01:00
|
|
|
python3 configure.py --selinux # enable SELinux integration
|
|
|
|
|
python3 configure.py --compile-commands # also write build/compile_commands.json
|
2026-08-27 19:08:08 +01:00
|
|
|
"""
|
|
|
|
|
import argparse
|
2026-08-27 22:50:44 +01:00
|
|
|
import json
|
2026-08-27 19:08:08 +01:00
|
|
|
import os
|
2026-08-27 22:50:44 +01:00
|
|
|
import shlex
|
2026-08-27 19:08:08 +01:00
|
|
|
import shutil
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
|
|
|
SRC = ROOT / "src"
|
|
|
|
|
BUILD = ROOT / "build"
|
|
|
|
|
|
|
|
|
|
CXX = os.environ.get("CXX", "g++")
|
|
|
|
|
CXXFLAGS = ["-std=c++20", "-O2", "-g"]
|
|
|
|
|
WARNINGS = ["-Wall", "-Wextra", "-Wpedantic", "-Wshadow", "-Wconversion", "-Wnull-dereference"]
|
|
|
|
|
linkflags = []
|
|
|
|
|
|
|
|
|
|
def discover_sources():
|
|
|
|
|
return sorted(
|
|
|
|
|
p.name for p in SRC.glob("*.cpp")
|
|
|
|
|
if not p.name.endswith("_main.cpp") and not p.name.startswith(".")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def write_makefile_convenience():
|
|
|
|
|
make = ROOT / "Makefile"
|
|
|
|
|
make.write_text(
|
|
|
|
|
"""# convenience wrapper around configure.py + ninja.
|
|
|
|
|
#
|
|
|
|
|
# make -> configure + build
|
|
|
|
|
# make configure -> regenerate build.ninja
|
|
|
|
|
# make clean -> remove build dir
|
|
|
|
|
# make format -> clang-format all sources (if available)
|
|
|
|
|
#
|
|
|
|
|
.PHONY: all configure clean format
|
|
|
|
|
all: configure
|
|
|
|
|
\t@ninja -C build
|
|
|
|
|
|
|
|
|
|
configure:
|
|
|
|
|
\t@python3 configure.py
|
|
|
|
|
|
|
|
|
|
clean:
|
|
|
|
|
\t@rm -rf build
|
|
|
|
|
|
|
|
|
|
format:
|
|
|
|
|
\t@command -v clang-format >/dev/null && clang-format -i src/*.cpp src/*.hpp || true
|
|
|
|
|
""",
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-08-27 22:50:44 +01:00
|
|
|
def emit_ninja(cxx, cxxflags, dst, write_cc=False):
|
2026-08-27 19:08:08 +01:00
|
|
|
sources = discover_sources()
|
|
|
|
|
objs = ["obj/" + Path(s).stem + ".o" for s in sources]
|
|
|
|
|
target = "bajia"
|
2026-08-27 22:50:44 +01:00
|
|
|
ctl_target = "bctl"
|
|
|
|
|
# bctl_main.cpp is a second shipped binary (the control client),
|
|
|
|
|
# excluded from the init objects by the *_main.cpp convention.
|
|
|
|
|
ctl_source = SRC / "bctl_main.cpp"
|
|
|
|
|
ctl_obj = "obj/bctl_main.o"
|
|
|
|
|
|
|
|
|
|
# (source, output) pairs for compile_commands.json.
|
|
|
|
|
cc_entries = [(SRC / s, o) for s, o in zip(sources, objs)]
|
|
|
|
|
cc_entries.append((ctl_source, ctl_obj))
|
2026-08-27 19:08:08 +01:00
|
|
|
|
|
|
|
|
rule_cxx = (
|
|
|
|
|
"rule cxx\n"
|
|
|
|
|
" command = {cxx} {cxxflags} -MMD -MF $out.d -c $in -o $out\n"
|
|
|
|
|
" depfile = $out.d\n"
|
|
|
|
|
" deps = gcc\n"
|
|
|
|
|
" description = CXX $out\n"
|
|
|
|
|
).format(cxx=cxx, cxxflags=" ".join(cxxflags))
|
|
|
|
|
|
|
|
|
|
rule_link = (
|
|
|
|
|
"rule link\n"
|
|
|
|
|
" command = {cxx} {ldflags} $in -o $out -lpthread\n"
|
|
|
|
|
" description = LINK $out\n"
|
|
|
|
|
).format(cxx=cxx, ldflags=" ".join(linkflags))
|
|
|
|
|
|
|
|
|
|
lines = ["# generated by configure.py -- do not edit", "ninja_required_version = 1.8", ""]
|
|
|
|
|
lines.append(rule_cxx)
|
|
|
|
|
lines.append(rule_link)
|
|
|
|
|
lines.append('build {target}: link {objs}'.format(target=target, objs=" ".join(objs)))
|
2026-08-27 22:50:44 +01:00
|
|
|
lines.append('build {ctl}: link {ctl_obj}'.format(ctl=ctl_target, ctl_obj=ctl_obj))
|
2026-08-27 19:08:08 +01:00
|
|
|
lines.append("")
|
|
|
|
|
for o, s in zip(objs, sources):
|
|
|
|
|
lines.append('build {o}: cxx {src}/{s}'.format(o=o, src=SRC, s=s))
|
2026-08-27 22:50:44 +01:00
|
|
|
lines.append('build {ctl_obj}: cxx {ctl_src}'.format(ctl_obj=ctl_obj, ctl_src=ctl_source))
|
2026-08-27 19:08:08 +01:00
|
|
|
lines.append("")
|
2026-08-27 22:50:44 +01:00
|
|
|
lines.append(
|
|
|
|
|
'build all: phony {target} {ctl}'.format(target=target, ctl=ctl_target))
|
2026-08-27 19:08:08 +01:00
|
|
|
lines.append("default all")
|
|
|
|
|
lines.append("")
|
|
|
|
|
|
2026-08-27 22:50:44 +01:00
|
|
|
if write_cc:
|
|
|
|
|
write_compile_commands(cxx, cxxflags, cc_entries)
|
|
|
|
|
|
2026-08-27 19:08:08 +01:00
|
|
|
(BUILD / "obj").mkdir(exist_ok=True)
|
|
|
|
|
dst.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
|
|
|
|
2026-08-27 22:50:44 +01:00
|
|
|
def write_compile_commands(cxx, cxxflags, src_out_pairs):
|
|
|
|
|
"""Write build/compile_commands.json for clangd / clang-tidy."""
|
|
|
|
|
entries = []
|
|
|
|
|
for src, out in src_out_pairs:
|
|
|
|
|
args = [cxx] + list(cxxflags) + ["-c", str(src), "-o", out]
|
|
|
|
|
entries.append({
|
|
|
|
|
"directory": str(BUILD.resolve()),
|
|
|
|
|
"file": str(src.resolve()),
|
|
|
|
|
"output": out,
|
|
|
|
|
"arguments": args,
|
|
|
|
|
"command": shlex.join(args),
|
|
|
|
|
})
|
|
|
|
|
(BUILD / "compile_commands.json").write_text(
|
|
|
|
|
json.dumps(entries, indent=1) + "\n", encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
|
2026-08-27 19:08:08 +01:00
|
|
|
def parse_args():
|
|
|
|
|
p = argparse.ArgumentParser(description="Configure bajia build (generates build.ninja)")
|
|
|
|
|
p.add_argument("--asan", action="store_true", help="enable address + UB sanitizers")
|
|
|
|
|
p.add_argument("--debug", action="store_true", help="disable -O2, enable -O0")
|
|
|
|
|
p.add_argument("--clean", action="store_true", help="remove build dir")
|
2026-08-27 22:50:44 +01:00
|
|
|
p.add_argument("--selinux", action="store_true",
|
|
|
|
|
help="enable SELinux integration (needs libselinux-dev; "
|
|
|
|
|
"see tools/run_vm.py --selinux for a VM test path)")
|
|
|
|
|
p.add_argument("--compile-commands", action="store_true",
|
|
|
|
|
help="write build/compile_commands.json (for clangd)")
|
2026-08-27 19:08:08 +01:00
|
|
|
return p.parse_args()
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
args = parse_args()
|
|
|
|
|
if args.clean:
|
|
|
|
|
shutil.rmtree(BUILD, ignore_errors=True)
|
|
|
|
|
print("removed build/")
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
if not shutil.which("ninja"):
|
|
|
|
|
print("error: ninja not found in PATH", file=sys.stderr)
|
|
|
|
|
return 1
|
|
|
|
|
|
2026-08-27 22:50:44 +01:00
|
|
|
# Wipe stale objects when compiler flags change: ninja doesn't track
|
|
|
|
|
# cflags, so a re-run with a different --selinux/--asan/--debug profile
|
|
|
|
|
# would otherwise link a half-stale tree.
|
|
|
|
|
stamp = {"selinux": args.selinux, "asan": args.asan, "debug": args.debug}
|
|
|
|
|
stamp_file = BUILD / ".configure.json"
|
|
|
|
|
if stamp_file.exists():
|
|
|
|
|
try:
|
|
|
|
|
prev = json.loads(stamp_file.read_text())
|
|
|
|
|
except (json.JSONDecodeError, OSError):
|
|
|
|
|
prev = None
|
|
|
|
|
if prev != stamp:
|
|
|
|
|
print("build configuration changed; cleaning build/")
|
|
|
|
|
shutil.rmtree(BUILD, ignore_errors=True)
|
|
|
|
|
|
2026-08-27 19:08:08 +01:00
|
|
|
BUILD.mkdir(exist_ok=True)
|
|
|
|
|
|
|
|
|
|
cxxflags = list(CXXFLAGS)
|
|
|
|
|
global linkflags
|
|
|
|
|
linkflags = []
|
|
|
|
|
if args.debug:
|
|
|
|
|
cxxflags = [f for f in cxxflags if f != "-O2"]
|
|
|
|
|
cxxflags += ["-O0"]
|
|
|
|
|
if args.asan:
|
|
|
|
|
cxxflags += ["-fsanitize=address,undefined", "-fno-omit-frame-pointer"]
|
|
|
|
|
linkflags += ["-fsanitize=address,undefined"]
|
2026-08-27 22:50:44 +01:00
|
|
|
if args.selinux:
|
|
|
|
|
cxxflags += ["-DBAJIA_SELINUX"]
|
|
|
|
|
linkflags += ["-lselinux"]
|
2026-08-27 19:08:08 +01:00
|
|
|
|
|
|
|
|
cxxflags = [f for f in cxxflags if f not in WARNINGS]
|
|
|
|
|
cxxflags = WARNINGS + cxxflags
|
|
|
|
|
|
2026-08-27 22:50:44 +01:00
|
|
|
emit_ninja(CXX, cxxflags, BUILD / "build.ninja", write_cc=args.compile_commands)
|
2026-08-27 19:08:08 +01:00
|
|
|
write_makefile_convenience()
|
2026-08-27 22:50:44 +01:00
|
|
|
stamp_file.write_text(json.dumps(stamp, indent=1) + "\n", encoding="utf-8")
|
2026-08-27 19:08:08 +01:00
|
|
|
|
|
|
|
|
print(f"configured {len(discover_sources())} sources -> build/build.ninja")
|
2026-08-27 22:50:44 +01:00
|
|
|
if args.compile_commands:
|
|
|
|
|
print("wrote build/compile_commands.json")
|
2026-08-27 19:08:08 +01:00
|
|
|
print("run: ninja -C build")
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
sys.exit(main())
|