[build] Move to python build scripts per module
This change moves Bonnibel from a separate project into the jsix tree, and alters the project configuration to be jsix-specific. (I stopped using bonnibel for any other projects, so it's far easier to make it a custom generator for jsix.) The build system now also uses actual python code in `*.module` files to configure modules instead of TOML files. Target configs (boot, kernel-mode, user-mode) now moved to separate TOML files under `configs/` and can inherit from one another.
This commit is contained in:
181
scripts/bonnibel/module.py
Normal file
181
scripts/bonnibel/module.py
Normal file
@@ -0,0 +1,181 @@
|
||||
|
||||
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])
|
||||
|
||||
if libs:
|
||||
build.variable("libs", ["${libs}"] + libs)
|
||||
|
||||
inputs = []
|
||||
implicits = []
|
||||
|
||||
for start in self.sources:
|
||||
source = start
|
||||
|
||||
while source and source.action:
|
||||
output = source.get_output('${module_dir}')
|
||||
|
||||
if source.action.rule:
|
||||
build.build(
|
||||
rule = source.action.rule,
|
||||
outputs = output.input,
|
||||
inputs = source.input,
|
||||
implicit = [f'${{source_root}}/{p}' for p in source.deps or tuple()],
|
||||
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
|
||||
self.sources.append(Source(self.root, path, **kwargs))
|
||||
Reference in New Issue
Block a user