1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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 and the thread library.
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())
|