344 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			344 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/uart",
 | |
|         "hal/xilinx",
 | |
|     ]
 | |
|     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/uart/xuartlite.c",
 | |
|         "hal/uart/xuartlite_intr.c",
 | |
|         "hal/uart/xuartlite_stats.c",
 | |
|         "hal/xilinx/xil_assert.c",
 | |
|         ])
 | |
| 
 | |
| lib = source_set("lib", [
 | |
|         "lib/async.cc",
 | |
|         "lib/lock.cc",
 | |
|         ])
 | |
| 
 | |
| 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, lib],
 | |
|             elf_out=f"{app}.elf",
 | |
|             bin_out=f"{app}.bin",
 | |
|     )
 | |
| 
 | |
| all = [
 | |
|         build_source_set(hal),
 | |
|         build_source_set(lib),
 | |
|         bootloader_image,
 | |
| 
 | |
|         app_image("helloworld"),
 | |
|         app_image("timer"),
 | |
|         app_image("uart"),
 | |
|         app_image("async", sources=[
 | |
|             "apps/async/main.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()
 |