diff --git a/osdk/builder.py b/osdk/builder.py index b85d3f2..5326649 100644 --- a/osdk/builder.py +++ b/osdk/builder.py @@ -39,7 +39,7 @@ def gen(out: TextIO, context: Context): writer.separator("Components") for instance in context.instances: - objects = instance.objsfiles() + objects = instance.objsfiles(context) writer.comment(f"Component: {instance.manifest.id}") writer.comment(f"Resolved: {', '.join(instance.resolved)}") @@ -53,7 +53,7 @@ def gen(out: TextIO, context: Context): writer.newline() if instance.isLib(): - writer.build(instance.libfile(), "ar", + writer.build(instance.libfile(context), "ar", list(map(lambda o: o[1], objects))) else: libraries: list[str] = [] @@ -67,9 +67,9 @@ def gen(out: TextIO, context: Context): if not reqInstance.isLib(): raise Exception(f"Component {req} is not a library") - libraries.append(reqInstance.outfile()) + libraries.append(reqInstance.outfile(context)) - writer.build(instance.binfile(), "ld", + writer.build(instance.binfile(context), "ld", list(map(lambda o: o[1], objects)) + libraries) writer.newline() @@ -79,32 +79,47 @@ def build(componentSpec: str, targetSpec: str, props: Props = {}) -> str: context = contextFor(targetSpec, props) target = context.target - shell.mkdir(target.builddir()) - ninjaPath = f"{target.builddir()}/build.ninja" + shell.mkdir(context.builddir()) + ninjaPath = f"{context.builddir()}/build.ninja" with open(ninjaPath, "w") as f: gen(f, context) - component = context.componentByName(componentSpec) + instance = context.componentByName(componentSpec) - if component is None: + if instance is None: raise Exception(f"Component {componentSpec} not found") - shell.exec(f"ninja", "-v", "-f", ninjaPath, component.outfile()) + shell.exec(f"ninja", "-v", "-f", ninjaPath, instance.outfile(context)) - return component.outfile() + return instance.outfile(context) -def buildAll(targetSpec: str, props: Props = {}) -> str: +class Paths: + bin: str + lib: str + obj: str + + def __init__(self, bin: str, lib: str, obj: str): + self.bin = bin + self.lib = lib + self.obj = obj + + +def buildAll(targetSpec: str, props: Props = {}) -> Paths: context = contextFor(targetSpec, props) target = context.target - shell.mkdir(target.builddir()) - ninjaPath = f"{target.builddir()}/build.ninja" + shell.mkdir(context.builddir()) + ninjaPath = f"{context.builddir()}/build.ninja" with open(ninjaPath, "w") as f: gen(f, context) shell.exec(f"ninja", "-v", "-f", ninjaPath) - return target.builddir() + return Paths( + context.builddir() + "/bin", + context.builddir() + "/lib", + context.builddir() + "/obj", + ) diff --git a/osdk/context.py b/osdk/context.py index e079235..23852ad 100644 --- a/osdk/context.py +++ b/osdk/context.py @@ -1,27 +1,29 @@ -from typing import cast +from typing import cast, Protocol from pathlib import Path -from osdk.model import TargetManifest, ComponentManifest, Props, Type +from osdk.model import TargetManifest, ComponentManifest, Props, Type, Tool from osdk.logger import Logger -from osdk import const, shell, jexpr, utils +from osdk import const, shell, jexpr, utils, rules logger = Logger("context") +class IContext(Protocol): + def builddir(self) -> str: + ... + + class ComponentInstance: - target: TargetManifest manifest: ComponentManifest sources: list[str] = [] resolved: list[str] = [] def __init__( self, - target: TargetManifest, manifest: ComponentManifest, sources: list[str], resolved: list[str]): - self.target = target self.manifest = manifest self.sources = sources self.resolved = resolved @@ -29,26 +31,27 @@ class ComponentInstance: def isLib(self): return self.manifest.type == Type.LIB - def binfile(self) -> str: - return f"{self.target.builddir()}/bin/{self.manifest.id}.out" + def binfile(self, context: IContext) -> str: + return f"{context.builddir()}/bin/{self.manifest.id}.out" - def objdir(self) -> str: - return f"{self.target.builddir()}/obj/{self.manifest.id}" + def objdir(self, context: IContext) -> str: + return f"{context.builddir()}/obj/{self.manifest.id}" - def objsfiles(self) -> list[tuple[str, str]]: + def objsfiles(self, context: IContext) -> list[tuple[str, str]]: return list( map( lambda s: ( - s, f"{self.objdir()}/{s.replace(self.manifest.dirname() + '/', '')}.o"), + s, f"{self.objdir(context)}/{s.replace(self.manifest.dirname() + '/', '')}.o"), self.sources)) - def libfile(self) -> str: - return f"{self.target.builddir()}/lib/{self.manifest.id}.a" + def libfile(self, context: IContext) -> str: + return f"{context.builddir()}/lib/{self.manifest.id}.a" - def outfile(self) -> str: + def outfile(self, context: IContext) -> str: if self.isLib(): - return self.libfile() - return self.binfile() + return self.libfile(context) + else: + return self.binfile(context) def cinclude(self) -> str: if "cpp-root-include" in self.manifest.props: @@ -57,13 +60,15 @@ class ComponentInstance: return str(Path(self.manifest.dirname()).parent) -class Context: +class Context(IContext): target: TargetManifest - instances: list[ComponentInstance] = [] + instances: list[ComponentInstance] + tools: dict[str, Tool] - def __init__(self, target: TargetManifest, instances: list[ComponentInstance]): + def __init__(self, target: TargetManifest, instances: list[ComponentInstance], tools: dict[str, Tool]): self.target = target self.instances = instances + self.tools = tools def componentByName(self, name: str) -> ComponentInstance | None: result = list(filter(lambda x: x.manifest.id == name, self.instances)) @@ -79,8 +84,11 @@ class Context: def cdefs(self) -> list[str]: return self.target.cdefs() + def hashid(self) -> str: + return utils.hash((self.target.props, str(self.tools)))[0:8] + def builddir(self) -> str: - return self.target.builddir() + return f"{const.BUILD_DIR}/{self.target.id}-{self.hashid()[:8]}" def loadAllTargets() -> list[TargetManifest]: @@ -172,7 +180,7 @@ def instanciate(componentSpec: str, components: list[ComponentManifest], target: if not enabled: return None - return ComponentInstance(target, manifest, sources, resolved[1:]) + return ComponentInstance(manifest, sources, resolved[1:]) def contextFor(targetSpec: str, props: Props) -> Context: @@ -180,6 +188,24 @@ def contextFor(targetSpec: str, props: Props) -> Context: target = loadTarget(targetSpec) components = loadAllComponents() + tools: dict[str, Tool] = {} + + 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 component in components: + for toolSpec in component.tools: + tool = component.tools[toolSpec] + + tools[toolSpec].args += tool.args components = filterDisabled(components, target) instances = cast(list[ComponentInstance], list(filter(lambda e: e != None, map(lambda c: instanciate( @@ -187,5 +213,6 @@ def contextFor(targetSpec: str, props: Props) -> Context: return Context( target, - instances + instances, + tools, ) diff --git a/osdk/model.py b/osdk/model.py index 2763faa..64fecb4 100644 --- a/osdk/model.py +++ b/osdk/model.py @@ -14,28 +14,35 @@ Props = dict[str, Any] class Type(Enum): + UNKNOWN = "unknown" TARGET = "target" LIB = "lib" EXE = "exe" class Manifest: - id: str - type: Type + id: str = "" + type: Type = Type.UNKNOWN path: str = "" - def __init__(self, json: Json, path: str): - if not "id" in json: - raise ValueError("Missing id") + def __init__(self, json: Json = None, path: str = "", strict=True, **kwargs): + if json is not None: + if not "id" in json: + raise ValueError("Missing id") - self.id = json["id"] + self.id = json["id"] - if not "type" in json: - raise ValueError("Missing type") + if not "type" in json and strict: + raise ValueError("Missing type") - self.type = Type(json["type"]) + self.type = Type(json["type"]) - self.path = path + self.path = path + elif strict: + raise ValueError("Missing json") + + for key in kwargs: + setattr(self, key, kwargs[key]) def __str__(self): return f"Manifest(id={self.id}, type={self.type}, path={self.path})" @@ -48,23 +55,34 @@ class Manifest: class Tool: - cmd: str - args: list[str] + cmd: str = "" + args: list[str] = [] files: list[str] = [] - def __init__(self, json: Json): - if not "cmd" in json: - raise ValueError("Missing cmd") - self.cmd = json["cmd"] + def __init__(self, json: Json = None, strict=True, **kwargs): + if json is not None: + if not "cmd" in json and strict: + raise ValueError("Missing cmd") - if not "args" in json: - raise ValueError("Missing args") - self.args = json["args"] + self.cmd = json.get("cmd", self.cmd) - self.files = json.get("files", []) + if not "args" in json and strict: + raise ValueError("Missing args") + + self.args = json.get("args", []) + + self.files = json.get("files", []) + elif strict: + raise ValueError("Missing json") + + for key in kwargs: + setattr(self, key, kwargs[key]) def __str__(self): - return f"Tool(cmd={self.cmd}, args={self.args})" + return f"Tool(cmd={self.cmd}, args={self.args}, files={self.files})" + + def __repr__(self): + return f"Tool({self.cmd})" class TargetManifest(Manifest): @@ -72,29 +90,21 @@ class TargetManifest(Manifest): tools: dict[str, Tool] routing: dict[str, str] - def __init__(self, json: Json, path: str): - super().__init__(json, path) + def __init__(self, json: Json = None, path: str = "", strict=True, **kwargs): + if json is not None: + if not "props" in json and strict: + raise ValueError("Missing props") - if not "props" in json: - raise ValueError("Missing props") + self.props = json["props"] - self.props = json["props"] + if not "tools" in json and strict: + raise ValueError("Missing tools") - if not "tools" in json: - raise ValueError("Missing tools") + self.tools = {k: Tool(v) for k, v in json["tools"].items()} - self.tools = {k: Tool(v) for k, v in json["tools"].items()} + self.routing = json.get("routing", {}) - self.routing = json.get("routing", {}) - - def __str__(self): - return f"TargetManifest(" + \ - "id={self.id}, " + \ - "type={self.type}, " + \ - "props={self.props}, " + \ - "tools={self.tools}, " + \ - "path={self.path}" + \ - ")" + super().__init__(json, path, strict, **kwargs) def __repr__(self): return f"TargetManifest({self.id})" @@ -102,15 +112,6 @@ class TargetManifest(Manifest): def route(self, componentSpec: str): return self.routing[componentSpec] if componentSpec in self.routing else componentSpec - def hashid(self) -> str: - return utils.hash((self.props, self.tools), cls=ModelEncoder) - - def builddir(self) -> str: - return f"{const.BUILD_DIR}/{self.id}-{self.hashid()[:8]}" - - def patch(self, toolSpec: str, args: list[str]): - self.tools[toolSpec].args += args - def cdefs(self) -> list[str]: defines: list[str] = [] @@ -128,31 +129,24 @@ class TargetManifest(Manifest): class ComponentManifest(Manifest): - decription: str - props: Props - enableIf: dict[str, list[Any]] - requires: list[str] - provides: list[str] + decription: str = "(No description)" + props: Props = {} + tools: dict[str, Tool] = {} + enableIf: dict[str, list[Any]] = {} + requires: list[str] = [] + provides: list[str] = [] - def __init__(self, json: Json, path: str): - super().__init__(json, path) + def __init__(self, json: Json = None, path: str = "", strict=True, **kwargs): + if json is not None: + self.decription = json.get("description", self.decription) + self.props = json.get("props", self.props) + self.tools = {k: Tool(v, strict=False) + for k, v in json.get("tools", {}).items()} + self.enableIf = json.get("enableIf", self.enableIf) + self.requires = json.get("requires", self.requires) + self.provides = json.get("provides", self.provides) - self.decription = json.get("description", "(No description)") - self.props = json.get("props", {}) - self.enableIf = json.get("enableIf", {}) - self.requires = json.get("requires", []) - self.provides = json.get("provides", []) - - def __str__(self): - return f"ComponentManifest(" + \ - "id={self.id}, " + \ - "type={self.type}, " + \ - "description={self.decription}, " + \ - "requires={self.requires}, " + \ - "provides={self.provides}, " + \ - "injects={self.injects}, " + \ - "deps={self.deps}, " + \ - "path={self.path})" + super().__init__(json, path, strict, **kwargs) def __repr__(self): return f"ComponentManifest({self.id})" @@ -170,47 +164,3 @@ class ComponentManifest(Manifest): return False return True - - -class ModelEncoder(JSONEncoder): - def default(self, o: Any): - if isinstance(o, Manifest): - return { - "id": o.id, - "type": o.type.value, - "path": o.path - } - - if isinstance(o, Type): - return o.value - - if isinstance(o, Tool): - return { - "cmd": o.cmd, - "args": o.args, - "files": o.files - } - - if isinstance(o, TargetManifest): - return { - "id": o.id, - "type": o.type.value, - "props": o.props, - "tools": o.tools, - "routing": o.routing, - "path": o.path - } - - if isinstance(o, ComponentManifest): - return { - "id": o.id, - "type": o.type.value, - "description": o.decription, - "props": o.props, - "enableIf": o.enableIf, - "requires": o.requires, - "provides": o.provides, - "path": o.path - } - - return super().default(o)