Files
pokeplatinum/tools/scripts/lsfgen.py

136 lines
4.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Generate LSF and Meson source lists from one manifest.
The manifest follows the same format as the LSF, replacing source objects and
paths to the object that Meson builds. The Meson source lists are built by a
split between .c and .s to produce appropriate inputs for configuration.
"""
import argparse
import enum
import pathlib
import re
import sys
import typing
class ManifestError(Exception):
lineno: int
def __init__(self, lineno: int, message: str):
super().__init__(message)
self.lineno = lineno
class Arguments(argparse.Namespace):
manifest: pathlib.Path = pathlib.Path(".")
prefix_dir: str = "main.nef.p"
dest_lsf: pathlib.Path = pathlib.Path("platinum.us/main.lsf")
dest_c: pathlib.Path = pathlib.Path("src/meson.build")
dest_asm: pathlib.Path = pathlib.Path("asm/meson.build")
class FileKind(enum.Enum):
c = enum.auto()
o = enum.auto()
asm = enum.auto()
class Parsed:
lsf_lines: list[str]
sources: dict[FileKind, list[pathlib.Path]]
def __init__(self):
self.lsf_lines = []
self.sources = { kind: [] for kind in FileKind }
OBJECT_RE = re.compile(r"^(\s*Object\s+)(\S+)(.*)$")
KIND_BY_EXT = {".c": FileKind.c, ".s": FileKind.asm, ".o": FileKind.o}
def parse_args() -> Arguments:
assert __doc__, "Missing docstring"
argp = argparse.ArgumentParser(description=__doc__.splitlines()[0])
_ = argp.add_argument("manifest", metavar="MANIFEST")
_ = argp.add_argument("--prefix-dir", metavar="PREFIX",
help="Use PREFIX as a directory prefix for source-objects in the generated LSF")
_ = argp.add_argument("--dest-lsf", metavar="OUT",
help="Write the generated LSF to OUT")
_ = argp.add_argument("--dest-c", metavar="OUT",
help="Write the generated C source-list to OUT")
_ = argp.add_argument("--dest-asm", metavar="OUT",
help="Write the generated ASM source-list to OUT")
args = Arguments()
_ = argp.parse_args(namespace=args)
return args
def parse_input(fp: typing.TextIO, prefix: str) -> Parsed:
parsed = Parsed()
seen: set[pathlib.Path] = set()
for lineno, line in enumerate(fp, 1):
match = OBJECT_RE.match(line)
if not match:
parsed.lsf_lines.append(line)
continue
path = pathlib.Path(match.group(2))
kind = KIND_BY_EXT.get(path.suffix)
# If specifying a precompiled object, assume the user knows what they're doing
if kind is FileKind.o:
parsed.lsf_lines.append(line)
continue
if kind is None:
raise ManifestError(lineno, f"{path}: unknown source type '{path.suffix}' (expected .c or .s)")
if path in seen:
raise ManifestError(lineno, f"{path}: duplicate listing")
seen.add(path)
parsed.sources[kind].append(path)
parsed.lsf_lines.append(f"{match.group(1)}{prefix}{str(path).replace('/', '_')}.o{match.group(3)}\n")
return parsed
def generate_meson(parsed: Parsed, kind: FileKind, manifest: pathlib.Path) -> str:
header = f"# Generated by tools/lsfgen from {manifest}. DO NOT EDIT!"
entries = "".join(f""" '{"/".join(path.parts[1:])}',\n""" for path in parsed.sources[kind])
variable = f"pokeplatinum_{kind.name}"
return f"{header}\n{variable} = files(\n{entries})"
def write_meson(target: pathlib.Path, parsed: Parsed, kind: FileKind, manifest: pathlib.Path) -> None:
if not parsed.sources[kind]:
return
with open(target, "w", encoding="utf-8") as fp:
_ = fp.write(generate_meson(parsed, kind, manifest))
def main() -> int:
args = parse_args()
with open(args.manifest, encoding="utf-8") as fp:
try:
parsed = parse_input(fp, args.prefix_dir)
except ManifestError as e:
print(f"{args.manifest}:{e.lineno}: {e}", file=sys.stderr)
return 1
with open(args.dest_lsf, "w", encoding="utf-8") as fp:
fp.writelines(parsed.lsf_lines)
write_meson(args.dest_c, parsed, FileKind.c, args.manifest)
write_meson(args.dest_asm, parsed, FileKind.asm, args.manifest)
return 0
if __name__ == "__main__":
sys.exit(main())