mbv: now with out of source builds

This commit is contained in:
Paul Mathieu 2025-06-23 22:06:27 -07:00
parent af768cbb09
commit e23cf79aa0

191
mbv/configure vendored
View File

@ -2,18 +2,25 @@
import argparse import argparse
import collections import collections
import dataclasses
import glob import glob
import itertools import itertools
import os import os
import re import re
# global config 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_path = f"/opt/xpack-riscv-none-elf-gcc-14.2.0-3"
toolchain_prefix = "riscv-none-elf-" toolchain_prefix = "riscv-none-elf-"
builddir = "build"
outdir = "out"
linker_script = "apps/app.ld"
hostcflags = "-g -std=c++20 -fprofile-instr-generate -fcoverage-mapping" hostcflags = "-g -std=c++20 -fprofile-instr-generate -fcoverage-mapping"
hostlibs = "-lgtest -lgmock -lgtest_main" hostlibs = "-lgtest -lgmock -lgtest_main"
hostldflags = "-fprofile-instr-generate -fcoverage-mapping" hostldflags = "-fprofile-instr-generate -fcoverage-mapping"
@ -22,11 +29,6 @@ include_dirs = [
"hal/uart", "hal/uart",
"hal/lib/common", "hal/lib/common",
] ]
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 = [ common_flags = [
"-g", "-g",
"-Wall", "-Wall",
@ -36,9 +38,6 @@ common_flags = [
"-ffunction-sections", "-ffunction-sections",
"-Oz", "-Oz",
] ]
cc_flags = common_flags
cxx_flags = common_flags + ["-std=c++20", "-fno-rtti", "-fno-exceptions", "-Wno-missing-field-initializers"]
ldflags = [ ldflags = [
"-Oz", "-Oz",
"-g", "-g",
@ -47,39 +46,48 @@ ldflags = [
"-flto", "-flto",
"-march=rv32i_zicsr", "-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(): @property
return cpp_flags + cxx_flags 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(): @property
return cpp_flags + cc_flags 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(): def gen_rules(config):
return ldflags
def add_cpp_flag(flag):
cpp_flags.append(flag)
def gen_rules():
tools = {"cxx": "g++", "cc": "gcc", "as": "as", "objcopy": "objcopy"} tools = {"cxx": "g++", "cc": "gcc", "as": "as", "objcopy": "objcopy"}
tc_path = toolchain_path tc_path = config.toolchain_path
tc_prefix = toolchain_prefix tc_prefix = config.toolchain_prefix
rules = f""" rules = f"""
rule cxx 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 description = CXX $out
depfile = $out.d depfile = $out.d
deps = gcc deps = gcc
rule cc 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 description = CC $out
depfile = $out.d depfile = $out.d
deps = gcc deps = gcc
@ -89,7 +97,7 @@ rule as
description = AS $out description = AS $out
rule link 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 description = LINK $out
rule objcopy rule objcopy
@ -97,13 +105,13 @@ rule objcopy
description = OBJCOPY $out description = OBJCOPY $out
rule hostcxx 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 description = HOSTCXX $out
depfile = $out.d depfile = $out.d
deps = gcc deps = gcc
rule hostlink rule hostlink
command = clang++ {hostldflags} -o $out $in {hostlibs} command = clang++ {config.hostldflags} -o $out $in {config.hostlibs}
description = HOSTLINK $out description = HOSTLINK $out
rule profdata rule profdata
@ -140,7 +148,7 @@ def get_suffix_rule(filename, cxx_rule="cxx", cc_rule="cc"):
def make_cxx_rule(name, cflags=()): def make_cxx_rule(name, cflags=()):
cflags = " ".join(cflags + get_cxx_flags()) cflags = " ".join(cflags)
rule = f""" rule = f"""
rule {name} rule {name}
command = $cxx -MMD -MT $out -MF $out.d {cflags} -c $in -o $out 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=()): def make_cc_rule(name, cflags=()):
cflags = " ".join(cflags + get_cc_flags()) cflags = " ".join(cflags)
rule = f""" rule = f"""
rule {name} rule {name}
command = $cc -MMD -MT $out -MF $out.d {cflags} -c $in -o $out command = $cc -MMD -MT $out -MF $out.d {cflags} -c $in -o $out
@ -163,55 +171,67 @@ rule {name}
return rule.splitlines() 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=()): def source_set(name, sources, cflags=()):
builds = [ return SourceSet(name, sources, cflags)
(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
def build_source_set(source_set): def build_source_set(source_set):
for line in source_set[1]: def __f(config):
yield line cxx_rule = "cxx"
cc_rule = "cc"
lines = []
def build_image(source_set, elf_out, dependencies=(), bin_out=None, linker_script=linker_script): if source_set.cflags:
# to make it builddir-relative cxx_rule = f"cxx_{name}"
linker_script = os.path.relpath(linker_script, builddir) lines += make_cxx_rule(cxx_rule, cflags=cflags + config.cxx_flags)
cc_rule = f"cc_{name}"
elf_out = os.path.relpath(elf_out, builddir) lines += make_cc_rule(cc_rule, cflags=cflags + config.cc_flags)
objects, lines = source_set
for line in lines: for line in lines:
yield line yield line
for objs, _ in dependencies:
objects += objs 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) objects = " ".join(objects)
yield f"build {elf_out}: link {objects} | {linker_script}" yield f"build {elfout}: link {objects} | {lscript}"
yield f" linker_script = {linker_script}" yield f" linker_script = {lscript}"
if bin_out is not None: if bin_out is not None:
bin_out = os.path.relpath(bin_out, builddir) binout = config.relbuild(os.path.join(config.outdir, bin_out))
yield f"build {bin_out}: objcopy {elf_out}" yield f"build {binout}: objcopy {elfout}"
return __f
def build_test(name, sources): def build_test(name, sources):
@ -258,16 +278,17 @@ bootloader = source_set("bootloader", glob.glob("./bootloader/**/*.cc", recursiv
bootloader_image = build_image( bootloader_image = build_image(
bootloader, bootloader,
dependencies=[hal], dependencies=[hal],
elf_out="out/bootloader.elf", elf_out="bootloader.elf",
linker_script="bootloader/bootloader.ld", linker_script="bootloader/bootloader.ld",
) )
def app_image(app): def app_image(app):
return build_image( return build_image(
source_set(app, glob.glob(f"./apps/{app}/**/*.cc", recursive=True)), source_set(app, glob.glob(f"./apps/{app}/**/*.cc", recursive=True)),
linker_script="apps/app.ld",
dependencies=[hal], dependencies=[hal],
elf_out=f"out/{app}.elf", elf_out=f"{app}.elf",
bin_out=f"out/{app}.bin", bin_out=f"{app}.bin",
) )
all = [ all = [
@ -284,24 +305,24 @@ def parse_args():
parser = argparse.ArgumentParser(description='Generate ninja build files.') parser = argparse.ArgumentParser(description='Generate ninja build files.')
parser.add_argument('--version', required=True, parser.add_argument('--version', required=True,
help='version tag (typically from `git describe`)') 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() return parser.parse_args()
def main(): def main():
args = parse_args() args = parse_args()
add_cpp_flag(f'-DGIT_VERSION_TAG=\\"{args.version}\\"') config = Config(builddir=args.build_dir, outdir=args.out_dir)
header = gen_rules() config.project_flags.append(f'-DGIT_VERSION_TAG=\\"{args.version}\\"')
lines = itertools.chain(header, *all) header = gen_rules(config)
lines = itertools.chain(header, *(f(config) for f in all))
if not os.path.exists(builddir): with open(os.path.join(config.builddir, "build.ninja"), "w") as f:
os.mkdir(builddir)
with open(os.path.join(builddir, "build.ninja"), "w") as f:
f.write("\n".join(lines)) f.write("\n".join(lines))
f.write("\n") f.write("\n")
print( 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}/'
) )