def resolve(path): if path.startswith('/') or path.startswith('$'): return path from pathlib import Path return str(Path(path).resolve()) class Module: __fields = { # name: (type, default) "kind": (str, "exe"), "output": (str, None), "targets": (set, ()), "deps": (set, ()), "includes": (tuple, ()), "sources": (tuple, ()), "variables": (dict, ()), "default": (bool, False), } def __init__(self, name, modfile, root, **kwargs): from .source import Source # Required fields self.root = root self.name = name self.modfile = modfile for name, data in self.__fields.items(): ctor, default = data value = kwargs.get(name, default) if value is not None: value = ctor(value) setattr(self, name, value) for name in kwargs: if not name in self.__fields: raise AttributeError(f"No attribute named {name} on Module") # Turn strings into real Source objects self.sources = [Source(root, f) for f in self.sources] # Filled by Module.update self.depmods = set() def __str__(self): return "Module {} {}\n\t{}".format(self.kind, self.name, "\n\t".join(map(str, self.sources))) @property def output(self): if self.__output is not None: return self.__output if self.kind == "lib": return f"lib{self.name}.a" else: return f"{self.name}.elf" @output.setter def output(self, value): self.__output = value @classmethod def update(cls, mods): from . import BonnibelError for mod in mods.values(): for dep in mod.deps: if not dep in mods: raise BonnibelError(f"module '{mod.name}' references unknown module '{dep}'") depmod = mods[dep] mod.depmods.add(depmod) target_mods = [mod for mod in mods.values() if mod.targets] for mod in target_mods: closed = set() children = set(mod.depmods) while children: child = children.pop() closed.add(child) child.targets |= mod.targets children |= {m for m in child.depmods if not m in closed} def generate(self, output): filename = str(output / f"{self.name}.ninja") with open(filename, "w") as buildfile: from pathlib import Path from ninja.ninja_syntax import Writer build = Writer(buildfile) build.comment("This file is automatically generated by bonnibel") build.newline() build.variable("module_dir", f"${{target_dir}}/{self.name}.dir") for key, value in self.variables.items(): build.variable(key, value) build.newline() includes = [self.root, "${module_dir}"] for include in self.includes: p = Path(include) if p.is_absolute(): if not p in includes: includes.append(str(p.resolve())) elif include != ".": includes.append(str(self.root / p)) includes.append(f"${{module_dir}}/{p}") libs = [] order_only = [] closed = set() children = set(self.depmods) while children: child = children.pop() closed.add(child) includes += [f"${{target_dir}}/{child.name}.dir/{i}" for i in child.includes] includes += [f"{child.root}/{i}" for i in child.includes] if child.kind == "lib": libs.append(f"${{target_dir}}/{child.output}") else: order_only.append(f"${{target_dir}}/{child.output}") children |= {m for m in child.depmods if not m in closed} if includes: build.variable("ccflags", ["${ccflags}"] + [f"-I{i}" for i in includes]) build.variable("asflags", ["${asflags}"] + [f"-I{i}" for i in includes]) if libs: build.variable("libs", ["${libs}"] + libs) inputs = [] implicits = [] for start in self.sources: source = start while source and source.action: output = source.output if source.action.rule: build.build( rule = source.action.rule, outputs = output.input, inputs = source.input, implicit = list(map(resolve, source.deps)), variables = {"name": source.name}, ) elif source.action.implicit: implicits.append(source.input) else: inputs.append(source.input) source = output build.newline() output = f"${{target_dir}}/{self.output}" dump = f"${{target_dir}}/{self.output}.dump" build.build( rule = self.kind, outputs = output, inputs = inputs, implicit = implicits + libs, order_only = order_only, ) build.newline() build.build( rule = "dump", outputs = dump, inputs = output, variables = {"name": self.name}, ) if self.default: build.newline() build.default(output) build.default(dump) def add_input(self, path, **kwargs): from .source import Source s = Source(self.root, path, **kwargs) self.sources.append(s) return str(s.output) def add_depends(self, paths, deps): for source in self.sources: if source.name in paths: source.add_deps(deps)