331 lines
8.6 KiB
Python
Executable File
331 lines
8.6 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",
|
|
"-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):
|
|
return build_image(
|
|
source_set(app, glob.glob(f"./apps/{app}/**/*.cc", recursive=True)),
|
|
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"),
|
|
]
|
|
|
|
|
|
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()
|