diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 17:49:34 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 17:49:34 +0300 |
| commit | 4384435941b8c7658856b381d071aee2e0035e89 (patch) | |
| tree | 5ece99c3dd1decb8ec3fd404c0ece2b5865a552a /tools/amalgamate.py | |
term: move the term core to subprojects/term
The core now builds, tests, and benchmarks alone, as a Meson subproject
with no dependency on the main tree.
- include/term/ holds term.h and the header-only unicode/. src/ holds
the core and its private headers. The main tree gets only
include/term through term_dep, so it cannot reach a private header.
- The term tests, the term benchmarks, the Kitty parser suite, and
tools/amalgamate.py and tools/generate_unicode.py move with it. The
tests run as term:<name>.
- The main build passes its -march (arch_flag) and b_ndebug to the
subproject. A directory configured before this commit needs
meson configure -Dterm:arch_flag=-march=... once.
- The term tests compile with -UNDEBUG. They do work inside assert.
- The subproject exports term_test_dep with term_api.h, for the main
tree's render, thumbnail, color, and selection tests.
- The Kitty bridge used struct vector2i and has not compiled since the
public API redesign. It builds again. 2 of its 14 tests fail: REP
after a tab, and a cursor report after a resize.
- dirgraph.py drops the term and unicode layers. Its self-test uses pty.
- The symbols test ignores the __odr_asan symbols that ASan adds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tools/amalgamate.py')
| -rw-r--r-- | tools/amalgamate.py | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/tools/amalgamate.py b/tools/amalgamate.py new file mode 100644 index 0000000..f2cdf3b --- /dev/null +++ b/tools/amalgamate.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Write the term core as one term.c and one term.h. + +term.h joins the public headers. term.c joins every source file and puts +each private header in at its first include. A host compiles term.c once, +with any C11 compiler, and links libm. + + amalgamate.py TERM_DIR OUT_DIR [DEPFILE] + +TERM_DIR is the directory with src/ and include/. DEPFILE lists every file +read, for the build system. +""" + +import re +import sys +from pathlib import Path + +INCLUDE = re.compile(r'^\s*#\s*include\s+"([^"]+)"') +PUBLIC = ("term_types.h", "term_input.h", "term.h") + + +class Joiner: + def __init__(self, root: Path): + self.root = root + # The sources find the public headers without the term/ prefix. + self.search = (root / "src", root / "include" / "term") + self.seen: set[Path] = set() + + def resolve(self, name: str, base: Path) -> Path: + for d in (base,) + self.search: + p = (d / name).resolve() + if p.is_file(): + return p + raise SystemExit(f"{base}: cannot find {name}") + + def emit(self, path: Path, out: list[str]) -> None: + """Copy path to out. Replace each local include with the file, + the first time only.""" + self.seen.add(path) + rel = path.relative_to(self.root) + out.append(f"/* ---- {rel} ---- */\n") + for line in path.read_text().splitlines(keepends=True): + m = INCLUDE.match(line) + if not m: + out.append(line) + continue + dep = self.resolve(m.group(1), path.parent) + if dep not in self.seen: + self.emit(dep, out) + out.append(f"/* ---- end {rel} ---- */\n") + + +def sources(src: Path) -> list[Path]: + files = sorted(src.glob("*.c")) + sorted((src / "osc").glob("*.c")) + return [f.resolve() for f in files] + + +def main() -> int: + if len(sys.argv) not in (3, 4): + print(__doc__, file=sys.stderr) + return 2 + root = Path(sys.argv[1]).resolve() + public = root / "include" / "term" + out_dir = Path(sys.argv[2]) + out_dir.mkdir(parents=True, exist_ok=True) + banner = "/* Generated by tools/amalgamate.py. Do not edit. */\n" + + header = Joiner(root) + text = [banner] + header.emit(public / "term.h", text) + (out_dir / "term.h").write_text("".join(text)) + + body = Joiner(root) + text = [banner, + "/* The private headers have helpers that only some platforms or\n" + " * callers outside the core use. Clang warns about them here. */\n" + "#if defined(__clang__)\n" + '#pragma clang diagnostic ignored "-Wunused-function"\n' + "#endif\n", + '#include "term.h"\n'] + # The public headers are in term.h, so the sources skip them. + body.seen.update((public / name).resolve() for name in PUBLIC) + for src in sources(root / "src"): + body.emit(src, text) + (out_dir / "term.c").write_text("".join(text)) + if len(sys.argv) == 4: + read = sorted(header.seen | body.seen) + deps = " ".join(str(p).replace(" ", "\\ ") for p in read) + Path(sys.argv[3]).write_text(f"{out_dir / 'term.c'}: {deps}\n") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) |
