synth/mbv/configure

341 lines
8.9 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import collections
import dataclasses
import glob
import itertools
import os
import re
class Config:
def __init__(self, builddir, outdir):
self.builddir = builddir
self.outdir = outdir
if not os.path.exists(builddir):
os.mkdir(builddir)
if not os.path.exists(outdir):
os.mkdir(outdir)
toolchain_path = f"/opt/xpack-riscv-none-elf-gcc-14.2.0-3"
toolchain_prefix = "riscv-none-elf-"
hostcflags = "-g -std=c++20 -fprofile-instr-generate -fcoverage-mapping"
hostlibs = "-lgtest -lgmock -lgtest_main"
hostldflags = "-fprofile-instr-generate -fcoverage-mapping"
include_dirs = [
"hal",
"hal/uart",
"hal/lib/common",
]
common_flags = [
"-g",
"-Wall",
"-Wextra",
"-flto",
"-march=rv32i_zicsr",
"-ffunction-sections",
"-Oz",
]
ldflags = [
"-Oz",
"-g",
"-Wl,--gc-sections",
"-Wl,--print-memory-usage",
"-specs=nano.specs",
"-flto",
"-march=rv32i_zicsr",
]
project_flags = [
]
@property
def include_flags(self):
return [f"-I{os.path.relpath(i, self.builddir)}" for i in self.include_dirs]
@property
def cpp_flags(self):
return ["-DNDEBUG"] + self.include_flags + self.project_flags
@property
def cc_flags(self):
return self.common_flags + self.cpp_flags
@property
def cxx_flags(self):
return self.common_flags + [
"-std=c++20",
"-fno-rtti",
"-fno-exceptions",
"-Wno-missing-field-initializers",
] + self.cpp_flags
def relbuild(self, path):
return os.path.relpath(path, self.builddir)
def gen_rules(config):
tools = {"cxx": "g++", "cc": "gcc", "as": "as", "objcopy": "objcopy"}
tc_path = config.toolchain_path
tc_prefix = config.toolchain_prefix
rules = f"""
rule cxx
command = $cxx -MMD -MT $out -MF $out.d {' '.join(config.cxx_flags)} -c $in -o $out
description = CXX $out
depfile = $out.d
deps = gcc
rule cc
command = $cc -MMD -MT $out -MF $out.d {' '.join(config.cc_flags)} -c $in -o $out
description = CC $out
depfile = $out.d
deps = gcc
rule as
command = $as $in -o $out
description = AS $out
rule link
command = $cxx {' '.join(config.ldflags)} -Wl,-T$linker_script -o $out $in $libs
description = LINK $out
rule objcopy
command = $objcopy -O binary $in $out
description = OBJCOPY $out
rule hostcxx
command = clang++ -MMD -MT $out -MF $out.d {config.hostcflags} -c $in -o $out
description = HOSTCXX $out
depfile = $out.d
deps = gcc
rule hostlink
command = clang++ {config.hostldflags} -o $out $in {config.hostlibs}
description = HOSTLINK $out
rule profdata
command = llvm-profdata merge -sparse $profraw -o $out
description = PROFDATA
rule cov
command = llvm-cov show --output-dir cov -format html --instr-profile $profdata $objects && touch $out
description = COV
rule hosttest
command = LLVM_PROFILE_FILE=$in.profraw ./$in && touch $out
"""
for var, tool in tools.items():
toolpath = os.path.join(tc_path, "bin", f"{tc_prefix}{tool}")
yield f"{var} = {toolpath}"
for line in rules.splitlines():
yield line
def get_suffix_rule(filename, cxx_rule="cxx", cc_rule="cc"):
suffix = filename.split(".")[-1]
return collections.defaultdict(
lambda: None,
{
"c": cc_rule,
"cc": cxx_rule,
"cpp": cxx_rule,
"s": "as",
},
)[suffix]
def make_cxx_rule(name, cflags=()):
cflags = " ".join(cflags)
rule = f"""
rule {name}
command = $cxx -MMD -MT $out -MF $out.d {cflags} -c $in -o $out
description = CXX $out
depfile = $out.d
deps = gcc
"""
return rule.splitlines()
def make_cc_rule(name, cflags=()):
cflags = " ".join(cflags)
rule = f"""
rule {name}
command = $cc -MMD -MT $out -MF $out.d {cflags} -c $in -o $out
description = CC $out
depfile = $out.d
deps = gcc
"""
return rule.splitlines()
@dataclasses.dataclass
class SourceSet:
name: str
sources: list[str]
cflags: list[str]
def get_objects(self, config):
for s in self.sources:
yield (config.relbuild(s), re.sub(r"\.\w+", ".o", s))
def get_targets(self, config):
return list(zip(*self.get_objects(config)))[1]
def source_set(name, sources, cflags=()):
return SourceSet(name, sources, cflags)
def build_source_set(source_set):
def __f(config):
cxx_rule = "cxx"
cc_rule = "cc"
lines = []
if source_set.cflags:
cxx_rule = f"cxx_{name}"
lines += make_cxx_rule(cxx_rule, cflags=cflags + config.cxx_flags)
cc_rule = f"cc_{name}"
lines += make_cc_rule(cc_rule, cflags=cflags + config.cc_flags)
for line in lines:
yield line
for i, o in source_set.get_objects(config):
rule = get_suffix_rule(i, cxx_rule=cxx_rule, cc_rule=cc_rule)
if rule is None:
continue
yield f"build {o}: {rule} {i}"
return __f
def build_image(source_set, elf_out, linker_script, dependencies=(), bin_out=None):
def __f(config):
# to make it builddir-relative
lscript = config.relbuild(linker_script)
elfout = config.relbuild(os.path.join(config.outdir, elf_out))
for l in build_source_set(source_set)(config):
yield l
objects = source_set.get_targets(config)
for dep in dependencies:
objects += dep.get_targets(config)
objects = " ".join(objects)
yield f"build {elfout}: link {objects} | {lscript}"
yield f" linker_script = {lscript}"
if bin_out is not None:
binout = config.relbuild(os.path.join(config.outdir, bin_out))
yield f"build {binout}: objcopy {elfout}"
return __f
def build_test(name, sources):
builds = [
(os.path.relpath(s, builddir), f"{name}_" + re.sub(r"\.\w+", ".o", s))
for s in sources
]
out = name
for i, o in builds:
rule = "hostcxx"
yield f"build {o}: {rule} {i}"
objects = " ".join(b[1] for b in builds)
yield f"build {out}: hostlink {objects}"
yield f"build {out}.run: hosttest {out}"
def make_coverage(binaries):
bins = " ".join(binaries)
profraw = " ".join(f"{x}.profraw" for x in binaries)
objects = " ".join(f"--object {x}" for x in binaries)
testruns = " ".join(f"{x}.run" for x in binaries)
yield f"build profdata: profdata | {testruns}"
yield f" profraw = {profraw}"
yield f"build cov/index.html: cov {bins} | profdata"
yield f" profdata = profdata"
yield f" objects = {objects}"
hal = source_set("hal", [
"hal/intc.cc",
"hal/interrupts.cc",
"hal/start.cc",
"hal/lib/common/xil_assert.c",
"hal/uart/xuartlite.c",
"hal/uart/xuartlite_stats.c",
"hal/uart/xuartlite_intr.c",
])
bootloader = source_set("bootloader", glob.glob("./bootloader/**/*.cc", recursive=True))
bootloader_image = build_image(
bootloader,
dependencies=[hal],
elf_out="bootloader.elf",
linker_script="bootloader/bootloader.ld",
)
def app_image(app, sources=None):
if sources is None:
sources = glob.glob(f"./apps/{app}/**/*.cc", recursive=True)
return build_image(
source_set(app, sources),
linker_script="apps/app.ld",
dependencies=[hal],
elf_out=f"{app}.elf",
bin_out=f"{app}.bin",
)
all = [
build_source_set(hal),
bootloader_image,
app_image("helloworld"),
app_image("timer"),
app_image("uart"),
app_image("async", sources=[
"apps/async/async.cc",
"apps/async/lock.cc",
"apps/async/main.cc",
"apps/async/trace.cc",
"apps/async/uart.cc",
]),
]
def parse_args():
parser = argparse.ArgumentParser(description='Generate ninja build files.')
parser.add_argument('--version', required=True,
help='version tag (typically from `git describe`)')
parser.add_argument('--build-dir', default='build', help='build directory')
parser.add_argument('--out-dir', default='out', help='output directory')
return parser.parse_args()
def main():
args = parse_args()
config = Config(builddir=args.build_dir, outdir=args.out_dir)
config.project_flags.append(f'-DGIT_VERSION_TAG=\\"{args.version}\\"')
header = gen_rules(config)
lines = itertools.chain(header, *(f(config) for f in all))
with open(os.path.join(config.builddir, "build.ninja"), "w") as f:
f.write("\n".join(lines))
f.write("\n")
print(
f'Configure done. Build with "ninja -C {config.builddir}". Output will be in {config.outdir}/'
)
if __name__ == "__main__":
main()