cutekit/osdk/context.py

251 lines
7.6 KiB
Python
Raw Normal View History

2023-02-07 11:40:00 +00:00
from typing import cast, Protocol
2023-02-02 22:05:45 +00:00
from pathlib import Path
2023-01-31 20:09:28 +00:00
2023-02-07 15:27:04 +00:00
from osdk.model import TargetManifest, ComponentManifest, Props, Type, Tool, Tools
from osdk.logger import Logger
from osdk import const, shell, jexpr, utils, rules, mixins
logger = Logger("context")
2023-02-07 11:40:00 +00:00
class IContext(Protocol):
def builddir(self) -> str:
...
class ComponentInstance:
enabled: bool = True
disableReason = ""
manifest: ComponentManifest
sources: list[str] = []
resolved: list[str] = []
def __init__(
self,
enabled: bool,
disableReason: str,
manifest: ComponentManifest,
sources: list[str],
resolved: list[str]):
self.enabled = enabled
self.disableReason = disableReason
self.manifest = manifest
self.sources = sources
self.resolved = resolved
2023-01-31 20:09:28 +00:00
def isLib(self):
return self.manifest.type == Type.LIB
2023-01-31 20:09:28 +00:00
2023-02-07 11:40:00 +00:00
def binfile(self, context: IContext) -> str:
return f"{context.builddir()}/bin/{self.manifest.id}.out"
2023-02-07 11:40:00 +00:00
def objdir(self, context: IContext) -> str:
return f"{context.builddir()}/obj/{self.manifest.id}"
2023-02-07 11:40:00 +00:00
def objsfiles(self, context: IContext) -> list[tuple[str, str]]:
return list(
map(
2023-01-31 20:09:28 +00:00
lambda s: (
2023-02-07 11:40:00 +00:00
s, f"{self.objdir(context)}/{s.replace(self.manifest.dirname() + '/', '')}.o"),
2023-01-31 20:09:28 +00:00
self.sources))
2023-02-07 11:40:00 +00:00
def libfile(self, context: IContext) -> str:
return f"{context.builddir()}/lib/{self.manifest.id}.a"
2023-02-07 11:40:00 +00:00
def outfile(self, context: IContext) -> str:
if self.isLib():
2023-02-07 11:40:00 +00:00
return self.libfile(context)
else:
return self.binfile(context)
2023-02-02 22:05:45 +00:00
def cinclude(self) -> str:
if "cpp-root-include" in self.manifest.props:
return self.manifest.dirname()
else:
return str(Path(self.manifest.dirname()).parent)
2023-02-07 11:40:00 +00:00
class Context(IContext):
target: TargetManifest
2023-02-07 11:40:00 +00:00
instances: list[ComponentInstance]
2023-02-07 15:27:04 +00:00
tools: Tools
2023-02-07 15:27:04 +00:00
def __init__(self, target: TargetManifest, instances: list[ComponentInstance], tools: Tools):
self.target = target
self.instances = instances
2023-02-07 11:40:00 +00:00
self.tools = tools
def componentByName(self, name: str) -> ComponentInstance | None:
result = list(filter(lambda x: x.manifest.id == name, self.instances))
if len(result) == 0:
return None
return result[0]
2023-02-06 10:14:32 +00:00
def cincls(self) -> list[str]:
2023-02-02 22:05:45 +00:00
includes = list(
map(lambda x: x.cinclude(), self.instances))
return utils.uniq(includes)
2023-02-06 10:14:32 +00:00
def cdefs(self) -> list[str]:
return self.target.cdefs()
2023-02-07 11:40:00 +00:00
def hashid(self) -> str:
return utils.hash((self.target.props, str(self.tools)))[0:8]
2023-02-06 11:36:23 +00:00
def builddir(self) -> str:
2023-02-07 11:40:00 +00:00
return f"{const.BUILD_DIR}/{self.target.id}-{self.hashid()[:8]}"
2023-02-06 11:36:23 +00:00
def loadAllTargets() -> list[TargetManifest]:
files = shell.find(const.TARGETS_DIR, ["*.json"])
return list(
map(lambda path: TargetManifest(jexpr.evalRead(path), path), files))
def loadTarget(id: str) -> TargetManifest:
try:
return next(filter(lambda t: t.id == id, loadAllTargets()))
except StopIteration:
raise Exception(f"Target '{id}' not found")
def loadAllComponents() -> list[ComponentManifest]:
files = shell.find(const.SRC_DIR, ["manifest.json"])
return list(
map(
lambda path: ComponentManifest(jexpr.evalRead(path), path),
files))
def filterDisabled(components: list[ComponentManifest], target: TargetManifest) -> tuple[list[ComponentManifest], list[ComponentManifest]]:
return list(filter(lambda c: c.isEnabled(target)[0], components)), \
list(filter(lambda c: not c.isEnabled(target)[0], components))
def providerFor(what: str, components: list[ComponentManifest]) -> tuple[str | None, str]:
result: list[ComponentManifest] = list(
filter(lambda c: c.id == what, components))
if len(result) == 0:
# Try to find a provider
result = list(filter(lambda x: (what in x.provides), components))
if len(result) == 0:
logger.error(f"No provider for '{what}'")
return (None, f"No provider for '{what}'")
if len(result) > 1:
ids = list(map(lambda x: x.id, result))
logger.error(f"Multiple providers for '{what}': {result}")
return (None, f"Multiple providers for '{what}': {','.join(ids)}")
return (result[0].id, "")
def resolveDeps(componentSpec: str, components: list[ComponentManifest], target: TargetManifest) -> tuple[bool, str, list[str]]:
mapping = dict(map(lambda c: (c.id, c), components))
def resolveInner(what: str, stack: list[str] = []) -> tuple[bool, str, list[str]]:
result: list[str] = []
what = target.route(what)
resolved, unresolvedReason = providerFor(what, components)
if resolved is None:
return False, unresolvedReason, []
if resolved in stack:
raise Exception(f"Dependency loop: {stack} -> {resolved}")
stack.append(resolved)
for req in mapping[resolved].requires:
keep, unresolvedReason, reqs = resolveInner(req, stack)
if not keep:
stack.pop()
logger.error(f"Dependency '{req}' not met for '{resolved}'")
return False, unresolvedReason, []
result.extend(reqs)
stack.pop()
2023-02-06 17:11:50 +00:00
result.insert(0, resolved)
return True, "", result
enabled, unresolvedReason, resolved = resolveInner(componentSpec)
return enabled, unresolvedReason, resolved
2023-01-31 20:09:28 +00:00
def instanciate(componentSpec: str, components: list[ComponentManifest], target: TargetManifest) -> ComponentInstance | None:
manifest = next(filter(lambda c: c.id == componentSpec, components))
sources = shell.find(
2023-02-06 10:14:32 +00:00
manifest.dirname(), ["*.c", "*.cpp", "*.s", "*.asm"], recusive=False)
enabled, unresolvedReason, resolved = resolveDeps(
componentSpec, components, target)
return ComponentInstance(enabled, unresolvedReason, manifest, sources, resolved[1:])
2023-01-31 20:09:28 +00:00
def instanciateDisabled(component: ComponentManifest, target: TargetManifest) -> ComponentInstance:
return ComponentInstance(False, component.isEnabled(target)[1], component, [], [])
2023-02-08 22:37:28 +00:00
context: dict = {}
def contextFor(targetSpec: str, props: Props = {}) -> Context:
2023-02-08 22:37:28 +00:00
if targetSpec in context:
return context[targetSpec]
logger.log(f"Loading context for '{targetSpec}'")
targetEls = targetSpec.split(":")
if targetEls[0] == "":
targetEls[0] = "host-" + shell.uname().machine
target = loadTarget(targetEls[0])
target.props |= props
components = loadAllComponents()
components, disabled = filterDisabled(components, target)
2023-02-07 15:27:04 +00:00
tools: Tools = {}
2023-02-07 11:40:00 +00:00
for toolSpec in target.tools:
tool = target.tools[toolSpec]
tools[toolSpec] = Tool(
strict=False,
cmd=tool.cmd,
args=tool.args,
files=tool.files)
tools[toolSpec].args += rules.rules[toolSpec].args
for m in targetEls[1:]:
mixin = mixins.byId(m)
tools = mixin(target, tools)
2023-02-07 11:40:00 +00:00
for component in components:
for toolSpec in component.tools:
tool = component.tools[toolSpec]
tools[toolSpec].args += tool.args
instances: list[ComponentInstance] = list(
map(lambda c: instanciateDisabled(c, target), disabled))
instances += cast(list[ComponentInstance], list(filter(lambda e: e != None, map(lambda c: instanciate(
2023-01-31 20:09:28 +00:00
c.id, components, target), components))))
2023-02-08 22:37:28 +00:00
context[targetSpec] = Context(
target,
2023-02-07 11:40:00 +00:00
instances,
tools,
)
2023-02-08 22:37:28 +00:00
return context[targetSpec]