# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. import functools import hashlib from os.path import dirname from pathlib import Path import jinja2 from buildconfig import config, topsrcdir # type: ignore from schema_parser import parse_and_validate THIS_DIR = Path(dirname(__file__)) TEMPLATES = THIS_DIR / "templates" @functools.cache def get_deps(): # Any imported python module is added as a dependency automatically, # so we only need the templates. return { *[str(p) for p in TEMPLATES.iterdir()], str(THIS_DIR / "gecko-trace.schema.yaml"), } def generate_cpp_events(output_fd, *inputs): """Generate C++ events header""" events = parse_and_validate(inputs if inputs else load_schema_index()) template = _jinja2_env().get_template("GeckoTraceEvents.h.jinja2") output_fd.write( template.render( events=events, enabled=config.substs.get("GECKO_TRACE_ENABLE", False), # Generate a unique hash to prevent include guard conflicts when # multiple event files are generated and included together (e.g., in gtests). # This ensures each generated header has a distinct include guard. input_hash=hashlib.sha256("".join(inputs).encode()) .hexdigest() .upper()[:15], ) ) return get_deps().union(load_schema_index() if not inputs else {}) def generate_glean_metrics(output_fd, *inputs): events = parse_and_validate(inputs if inputs else load_schema_index()) template = _jinja2_env().get_template("generated-metrics.yaml.jinja2") output_fd.write( template.render( events=events, ) ) def generate_glean_adapter(output_fd, *inputs): events = parse_and_validate(inputs if inputs else load_schema_index()) template = _jinja2_env().get_template("glean_adapter.rs.jinja2") output_fd.write( template.render( events=events, ) ) return get_deps().union(load_schema_index() if not inputs else {}) @functools.cache def load_schema_index(): index = THIS_DIR.parent / "index.py" with open(index) as f: index_src = f.read() namespace = {} exec(index_src, namespace) return [str(Path(topsrcdir) / x) for x in namespace["gecko_trace_files"]] @functools.cache def _jinja2_env(): from jinja2.exceptions import TemplateRuntimeError from jinja2.ext import Extension class RaiseExtension(Extension): tags = set(["raise"]) def parse(self, parser): lineno = next(parser.stream).lineno message_node = parser.parse_expression() return jinja2.nodes.CallBlock( self.call_method("_raise", [message_node], lineno=lineno), [], [], [], lineno=lineno, ) def _raise(self, msg, caller): raise TemplateRuntimeError(msg) def camelize(name): """Convert snake_case to PascalCase""" return "".join( word.capitalize() for word in name.replace("-", "_").replace(".", "_").split("_") ) def snake_case(name): """Convert name to snake_case""" return name.replace(".", "_").replace("-", "_").lower() def debug(value): print(value) return value env = jinja2.Environment( loader=jinja2.FileSystemLoader(TEMPLATES), trim_blocks=True, lstrip_blocks=True, extensions=[RaiseExtension], ) env.filters["camelize"] = camelize env.filters["snake_case"] = snake_case env.filters["debug"] = debug return env def main(*args): # mach requires this pass