mirror of
https://github.com/justinian/jsix.git
synced 2025-12-10 00:14:32 -08:00
The initrd image is now created by the build system, loaded by the bootloader, and passed to srv.init, which loads it (but doesn't do anything with it yet, so this is actually a functional regression). This simplifies a lot of the modules code between boot and init as well: Gone are the many subclasses of module and all the data being inline with the module structs, except for any loaded files. Now the only modules loaded and passed will be the initrd, and any devices only the bootloader has knowledge of, like the UEFI framebuffer.
277 lines
8.8 KiB
Python
277 lines
8.8 KiB
Python
from . import include_rel, mod_rel, target_rel
|
|
|
|
def resolve(path):
|
|
if path.startswith('/') or path.startswith('$'):
|
|
return path
|
|
from pathlib import Path
|
|
return str(Path(path).resolve())
|
|
|
|
class BuildOptions:
|
|
def __init__(self, includes=tuple(), libs=tuple(), order_only=tuple()):
|
|
self.includes = list(includes)
|
|
self.libs = list(libs)
|
|
self.order_only = list(order_only)
|
|
|
|
|
|
class Module:
|
|
__fields = {
|
|
# name: (type, default)
|
|
"kind": (str, "exe"),
|
|
"output": (str, None),
|
|
"targets": (set, ()),
|
|
"deps": (set, ()),
|
|
"public_headers": (set, ()),
|
|
"includes": (tuple, ()),
|
|
"sources": (tuple, ()),
|
|
"drivers": (tuple, ()),
|
|
"variables": (dict, ()),
|
|
"default": (bool, False),
|
|
"description": (str, None),
|
|
"no_libc": (bool, False),
|
|
}
|
|
|
|
def __init__(self, name, modfile, root, **kwargs):
|
|
from .source import make_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 = [make_source(root, f) for f in self.sources]
|
|
self.public_headers = [make_source(root, f) for f in self.public_headers]
|
|
|
|
# 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
|
|
|
|
def resolve(source, modlist):
|
|
resolved = set()
|
|
for dep in modlist:
|
|
if not dep in mods:
|
|
raise BonnibelError(f"module '{source.name}' references unknown module '{dep}'")
|
|
mod = mods[dep]
|
|
resolved.add(mod)
|
|
return resolved
|
|
|
|
for mod in mods.values():
|
|
mod.depmods = resolve(mod, mod.deps)
|
|
|
|
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):
|
|
from pathlib import Path
|
|
from collections import defaultdict
|
|
from ninja.ninja_syntax import Writer
|
|
|
|
def walk_deps(deps):
|
|
open_set = set(deps)
|
|
closed_set = set()
|
|
while open_set:
|
|
dep = open_set.pop()
|
|
closed_set.add(dep)
|
|
open_set |= {m for m in dep.depmods if not m in closed_set}
|
|
return closed_set
|
|
|
|
all_deps = walk_deps(self.depmods)
|
|
|
|
def gather_phony(build, deps, child_rel, add_headers=False):
|
|
phony = ".headers.phony"
|
|
child_phony = [child_rel(phony, module=c.name)
|
|
for c in all_deps]
|
|
|
|
header_targets = []
|
|
if add_headers:
|
|
if not self.no_libc:
|
|
header_targets.append(f"${{build_root}}/include/libc/{phony}")
|
|
if self.public_headers:
|
|
header_targets.append(f"${{build_root}}/include/{self.name}/{phony}")
|
|
|
|
build.build(
|
|
rule = "touch",
|
|
outputs = [mod_rel(phony)],
|
|
implicit = child_phony + header_targets,
|
|
order_only = list(map(mod_rel, deps)),
|
|
)
|
|
|
|
filename = str(output / f"headers.{self.name}.ninja")
|
|
with open(filename, "w") as buildfile:
|
|
build = Writer(buildfile)
|
|
|
|
build.comment("This file is automatically generated by bonnibel")
|
|
build.newline()
|
|
|
|
build.variable("module_dir", f"${{build_root}}/include/{self.name}")
|
|
|
|
header_deps = []
|
|
|
|
inputs = []
|
|
headers = set(self.public_headers)
|
|
while headers:
|
|
source = headers.pop()
|
|
headers.update(source.next)
|
|
|
|
if source.action:
|
|
build.newline()
|
|
build.build(rule=source.action, **source.args)
|
|
|
|
if source.gather:
|
|
header_deps += list(source.outputs)
|
|
|
|
if source.input:
|
|
inputs.extend(map(mod_rel, source.outputs))
|
|
|
|
build.newline()
|
|
gather_phony(build, header_deps, include_rel)
|
|
|
|
filename = str(output / f"module.{self.name}.ninja")
|
|
with open(filename, "w") as buildfile:
|
|
build = Writer(buildfile)
|
|
|
|
build.comment("This file is automatically generated by bonnibel")
|
|
build.newline()
|
|
|
|
build.variable("module_dir", target_rel(self.name + ".dir"))
|
|
|
|
modopts = BuildOptions(
|
|
includes = [self.root, "${module_dir}"],
|
|
)
|
|
if self.public_headers:
|
|
modopts.includes += [f"${{build_root}}/include/{self.name}"]
|
|
|
|
for key, value in self.variables.items():
|
|
build.variable(key, value)
|
|
build.newline()
|
|
|
|
for include in self.includes:
|
|
p = Path(include)
|
|
if p.is_absolute():
|
|
if not p in modopts.includes:
|
|
modopts.includes.append(str(p.resolve()))
|
|
elif include != ".":
|
|
incpath = self.root / p
|
|
destpath = mod_rel(p)
|
|
for header in incpath.rglob("*.h"):
|
|
dest_header = f"{destpath}/" + str(header.relative_to(incpath))
|
|
modopts.includes.append(str(incpath))
|
|
modopts.includes.append(destpath)
|
|
|
|
all_deps = walk_deps(self.depmods)
|
|
for dep in all_deps:
|
|
if dep.public_headers:
|
|
modopts.includes += [f"${{build_root}}/include/{dep.name}"]
|
|
|
|
if dep.kind == "lib":
|
|
modopts.libs.append(target_rel(dep.output))
|
|
else:
|
|
modopts.order_only.append(target_rel(dep.output))
|
|
|
|
if modopts.includes:
|
|
build.variable("ccflags", ["${ccflags}"] + [f"-I{i}" for i in modopts.includes])
|
|
build.variable("asflags", ["${asflags}"] + [f"-I{i}" for i in modopts.includes])
|
|
|
|
if modopts.libs:
|
|
build.variable("libs", ["${libs}"] + modopts.libs)
|
|
|
|
header_deps = []
|
|
|
|
inputs = []
|
|
sources = set(self.sources)
|
|
while sources:
|
|
source = sources.pop()
|
|
sources.update(source.next)
|
|
|
|
if source.action:
|
|
build.newline()
|
|
build.build(rule=source.action, **source.args)
|
|
|
|
if source.gather:
|
|
header_deps += list(source.outputs)
|
|
|
|
if source.input:
|
|
inputs.extend(map(mod_rel, source.outputs))
|
|
|
|
build.newline()
|
|
|
|
gather_phony(build, header_deps, target_rel, add_headers=True)
|
|
|
|
output = target_rel(self.output)
|
|
dump = output + ".dump"
|
|
build.newline()
|
|
build.build(
|
|
rule = self.kind,
|
|
outputs = output,
|
|
inputs = inputs,
|
|
implicit = modopts.libs,
|
|
order_only = modopts.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 s.outputs
|
|
|
|
def add_depends(self, paths, deps):
|
|
for source in self.sources:
|
|
if source.path in paths:
|
|
source.add_deps(deps)
|
|
|
|
for source in self.public_headers:
|
|
if source.path in paths:
|
|
source.add_deps(deps)
|