From e23cf79aa0685053e32327007c7f6e43535229bd Mon Sep 17 00:00:00 2001 From: Paul Mathieu Date: Mon, 23 Jun 2025 22:06:27 -0700 Subject: [PATCH] mbv: now with out of source builds --- mbv/configure | 241 +++++++++++++++++++++++++++----------------------- 1 file changed, 131 insertions(+), 110 deletions(-) diff --git a/mbv/configure b/mbv/configure index 6fcd407..add70d6 100755 --- a/mbv/configure +++ b/mbv/configure @@ -2,84 +2,92 @@ import argparse import collections +import dataclasses import glob import itertools import os import re -# global config -toolchain_path = f"/opt/xpack-riscv-none-elf-gcc-14.2.0-3" -toolchain_prefix = "riscv-none-elf-" -builddir = "build" -outdir = "out" -linker_script = "apps/app.ld" -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", -] +class Config: + def __init__(self, builddir, outdir): + self.builddir = builddir + self.outdir = outdir -include_flags = [f"-I{os.path.relpath(i, builddir)}" for i in include_dirs] -project_flags = [ -] -cpp_flags = ["-DNDEBUG"] + include_flags + project_flags -common_flags = [ - "-g", - "-Wall", - "-Wextra", - "-flto", - "-march=rv32i_zicsr", - "-ffunction-sections", - "-Oz", -] + if not os.path.exists(builddir): + os.mkdir(builddir) + if not os.path.exists(outdir): + os.mkdir(outdir) -cc_flags = common_flags -cxx_flags = common_flags + ["-std=c++20", "-fno-rtti", "-fno-exceptions", "-Wno-missing-field-initializers"] -ldflags = [ - "-Oz", - "-g", - "-Wl,--gc-sections", - "-Wl,--print-memory-usage", - "-flto", - "-march=rv32i_zicsr", -] + 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] -def get_cxx_flags(): - return cpp_flags + cxx_flags + @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 -def get_cc_flags(): - return cpp_flags + cc_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 get_ldflags(): - return ldflags - - -def add_cpp_flag(flag): - cpp_flags.append(flag) - - -def gen_rules(): +def gen_rules(config): tools = {"cxx": "g++", "cc": "gcc", "as": "as", "objcopy": "objcopy"} - tc_path = toolchain_path - tc_prefix = toolchain_prefix + tc_path = config.toolchain_path + tc_prefix = config.toolchain_prefix rules = f""" rule cxx - command = $cxx -MMD -MT $out -MF $out.d {' '.join(get_cxx_flags())} -c $in -o $out + 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(get_cc_flags())} -c $in -o $out + command = $cc -MMD -MT $out -MF $out.d {' '.join(config.cc_flags)} -c $in -o $out description = CC $out depfile = $out.d deps = gcc @@ -89,7 +97,7 @@ rule as description = AS $out rule link - command = $cxx {' '.join(ldflags)} -Wl,-T$linker_script -o $out $in $libs + command = $cxx {' '.join(config.ldflags)} -Wl,-T$linker_script -o $out $in $libs description = LINK $out rule objcopy @@ -97,13 +105,13 @@ rule objcopy description = OBJCOPY $out rule hostcxx - command = clang++ -MMD -MT $out -MF $out.d {hostcflags} -c $in -o $out + 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++ {hostldflags} -o $out $in {hostlibs} + command = clang++ {config.hostldflags} -o $out $in {config.hostlibs} description = HOSTLINK $out rule profdata @@ -140,7 +148,7 @@ def get_suffix_rule(filename, cxx_rule="cxx", cc_rule="cc"): def make_cxx_rule(name, cflags=()): - cflags = " ".join(cflags + get_cxx_flags()) + cflags = " ".join(cflags) rule = f""" rule {name} command = $cxx -MMD -MT $out -MF $out.d {cflags} -c $in -o $out @@ -152,7 +160,7 @@ rule {name} def make_cc_rule(name, cflags=()): - cflags = " ".join(cflags + get_cc_flags()) + cflags = " ".join(cflags) rule = f""" rule {name} command = $cc -MMD -MT $out -MF $out.d {cflags} -c $in -o $out @@ -163,55 +171,67 @@ rule {name} 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=()): - builds = [ - (os.path.relpath(s, builddir), re.sub(r"\.\w+", ".o", s)) - for s in sources - if get_suffix_rule(s) is not None - ] - - lines = [] - - cxx_rule = "cxx" - cc_rule = "cc" - if cflags: - cxx_rule = f"cxx_{name}" - lines += make_cxx_rule(cxx_rule, cflags=cflags) - cc_rule = f"cc_{name}" - lines += make_cc_rule(cc_rule, cflags=cflags) - - for i, o in builds: - rule = get_suffix_rule(i, cxx_rule=cxx_rule, cc_rule=cc_rule) - if rule is None: - continue - lines.append(f"build {o}: {rule} {i}") - - return [b[1] for b in builds], lines + return SourceSet(name, sources, cflags) def build_source_set(source_set): - for line in source_set[1]: - yield line + 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, dependencies=(), bin_out=None, linker_script=linker_script): - # to make it builddir-relative - linker_script = os.path.relpath(linker_script, builddir) +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)) - elf_out = os.path.relpath(elf_out, builddir) + for l in build_source_set(source_set)(config): + yield l - objects, lines = source_set - for line in lines: - yield line - for objs, _ in dependencies: - objects += objs - objects = " ".join(objects) + objects = source_set.get_targets(config) + for dep in dependencies: + objects += dep.get_targets(config) + objects = " ".join(objects) - yield f"build {elf_out}: link {objects} | {linker_script}" - yield f" linker_script = {linker_script}" - if bin_out is not None: - bin_out = os.path.relpath(bin_out, builddir) - yield f"build {bin_out}: objcopy {elf_out}" + 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): @@ -258,16 +278,17 @@ bootloader = source_set("bootloader", glob.glob("./bootloader/**/*.cc", recursiv bootloader_image = build_image( bootloader, dependencies=[hal], - elf_out="out/bootloader.elf", + 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"out/{app}.elf", - bin_out=f"out/{app}.bin", + elf_out=f"{app}.elf", + bin_out=f"{app}.bin", ) all = [ @@ -284,24 +305,24 @@ 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() - add_cpp_flag(f'-DGIT_VERSION_TAG=\\"{args.version}\\"') - header = gen_rules() - lines = itertools.chain(header, *all) + 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)) - if not os.path.exists(builddir): - os.mkdir(builddir) - - with open(os.path.join(builddir, "build.ninja"), "w") as f: + 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 {builddir}". Output will be in {outdir}/' + f'Configure done. Build with "ninja -C {config.builddir}". Output will be in {config.outdir}/' )