import argparse import os.path import pydoc import sys from pathlib import Path from typing import List, Union from jinja2 import Environment, PackageLoader, select_autoescape import ee.tools from ee.kicad import read_schematics from ee.project import Project, SupplierDescriptor class NinjaSupplier(object): def __init__(self, project: Project, souffle_ee_src: Path, supplier: SupplierDescriptor): self.project = project self.souffle_ee_src = souffle_ee_src self.supplier = supplier self.key = supplier.key @property def part_db(self): return "$public_dir/{}/parts.xml".format(self.key) @property def bom_input(self): return "$public_dir/{}/bom.xml".format(self.key) @property def bom_output(self): # This needs to be configurable per supplier return "$public_dir/{}/bom.csv".format(self.key) @property def souffle_script(self): build_it = self.souffle_ee_src / (self.key + ".dl") custom = self.project.public_dir / "custom" / (self.key + "-souffle.dl") return custom if custom.is_file() else build_it def ninja_path_filter(s: Union[Path, str, List[str]]) -> str: if isinstance(s, Path): s = str(s) if isinstance(s, str): return s. \ replace("$", "$$"). \ replace(" ", "$ ") elif isinstance(s, list): if len(s) == 1: return ninja_path_filter(s[0]) return "$\n" + " $\n".join([" " + ninja_path_filter(path) for path in s]) else: raise Exception("Unsupported argument type: {}".format(type(s))) def parent_dir_filter(s: Union[str, Path]) -> str: return str(Path(s).parent) def basename_filter(s: Union[str, Path]) -> str: return os.path.basename(str(s)) def noext_filter(s: Union[str, Path]) -> str: return os.path.splitext(os.path.basename(str(s)))[0] def generate(project: Project): def _create_env(): e = Environment( loader=PackageLoader(__name__, "templates"), autoescape=select_autoescape(["html", "xml"]), keep_trailing_newline=True, ) e.filters["ninja_path"] = ninja_path_filter e.filters["parent_dir"] = parent_dir_filter e.filters["basename"] = basename_filter e.filters["noext"] = noext_filter return e souffle_ee_src = Path(__file__).parent.parent / "souffle" params = { "ee": "{} -m ee".format(os.path.relpath(sys.executable, Path("."))), "project": project, "souffle_ee_src": str(souffle_ee_src), "is_file": lambda p: os.path.isfile(str(p)), } kicad_souffle_dl = project.public_dir / "custom" / "kicad-souffle.dl" if kicad_souffle_dl.is_file(): params["kicad_souffle_dl"] = str(kicad_souffle_dl) gerber_zip = None if project.cfg.has_section("kicad-project"): kp = project.cfg["kicad-project"] if "sch" in kp: sch_path = Path(project.cfg["kicad-project"]["sch"]) sch = read_schematics(str(sch_path)) sch_files = sorted([s.path for s in sch.schematics]) params["sch"] = sch_path params["sch_files"] = sch_files if "pcb" in kp: params["pcb"] = Path(project.cfg["kicad-project"]["pcb"]) gerber_zip = "prod/gerber.zip" params["suppliers"] = [NinjaSupplier(project, souffle_ee_src, s) for s in project.suppliers] if gerber_zip is not None: params["gerber_zip"] = gerber_zip # ee_dir = sch_path.parent / "ee" build_ninja = project.project_dir / "build.ninja" ee_ninja = project.project_dir / "ee.ninja" parts_yaml_files = [path for path in project.project_dir.iterdir() if str(path).endswith("-parts.yaml")] params["parts_yaml_files"] = parts_yaml_files if not build_ninja.exists(): with build_ninja.open("w") as f: f.writelines(["# This file was generated by eetools. Add your own rules and build commands\n", "# here, but keep the include line as we'll update ee.ninja for you when your\n", "# configuration changes\n", "\n", "include ee.ninja\n", "\n", "# This will build all reports by default\n", "default ee-reports\n", ]) # Hooks hooks = [("odoo", "ee.odoo.generate_ninja")] hook_fragments = {} for name, function in hooks: f = pydoc.locate(function) hook_fragments[name] = f(project) with ee_ninja.open("w") as f: env = _create_env() template = env.get_template("build.ninja.j2") f.write(template.render(**params)) f.write("\n") if len(hooks): for name, _ in hooks: fragment_name = hook_fragments[name] if fragment_name is None: continue f.write("# Hook: {}\n".format(name)) f.write(fragment_name) if not fragment_name.endswith("\n"): f.write("\n") parser = argparse.ArgumentParser() ee.tools.add_default_argparse_group(parser) args = parser.parse_args() ee.tools.process_default_argparse_group(args) generate(Project.load())