# 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 http://mozilla.org/MPL/2.0/. import string import sys from os import path import yaml ############################################################################### # Language-agnostic functionality # ############################################################################### template_header = ( "/* This file was autogenerated by " "toolkit/crashreporter/annotations/generate.py. DO NOT EDIT */\n\n" ) def read_template(template_filename): """Read the contents of the template. If an error is encountered quit the program.""" try: with open(template_filename) as template_file: template = template_file.read() except OSError as ex: sys.exit("Error when reading " + template_filename + ":\n" + str(ex) + "\n") return template def extract_crash_scope_list(annotations, scope): """Extract an array holding the names of the annotations allowed for inclusion in a crash scope.""" return [ name for (name, data) in annotations if data.get("scope", "client") == scope ] def extract_skiplist(annotations): """Extract an array holding the names of the annotations that should be skipped and the values which will cause them to be skipped.""" return [ (name, data.get("skip_if")) for (name, data) in annotations if len(data.get("skip_if", "")) > 0 ] def type_to_enum(annotation_type): """Emit the enum value corresponding to each annotation type.""" if annotation_type == "string": return "String" elif annotation_type == "boolean": return "Boolean" elif annotation_type == "u32": return "U32" elif annotation_type == "u64": return "U64" elif annotation_type == "usize": return "USize" elif annotation_type == "object": return "Object" def extract_types(annotations): """Extract an array holding the type of each annotation.""" return [type_to_enum(data.get("type")) for (_, data) in annotations] generators = {} def content_generator(*extensions): """Create a function that generates the content for a file extension.""" def f(func): for e in extensions: generators[e] = func return f ############################################################################### # C++ code generation # ############################################################################### def generate_strings(annotations): """Generate strings corresponding to every annotation.""" names = [' "' + data.get("altname", name) + '"' for (name, data) in annotations] return ",\n".join(names) def generate_enum(annotations): """Generate the C++ typed enum holding all the annotations and return it as a string.""" enum = "" for i, (name, _) in enumerate(annotations): enum += " " + name + " = " + str(i) + ",\n" enum += " Count = " + str(len(annotations)) return enum def generate_annotations_array_initializer(contents): """Generates the initializer for a C++ array of annotations.""" initializer = [" Annotation::" + name for name in contents] return ",\n".join(initializer) def generate_skiplist_initializer(contents): """Generates the initializer for a C++ array of AnnotationSkipValue structs.""" initializer = [ " { Annotation::" + name + ', "' + value + '" }' for (name, value) in contents ] return ",\n".join(initializer) def generate_types_initializer(contents): """Generates the initializer for a C++ array of AnnotationType values.""" initializer = [" AnnotationType::" + typename for typename in contents] return ",\n".join(initializer) @content_generator("h") def emit_header(annotations, _output_name): pingallowedlist = extract_crash_scope_list(annotations, "ping") pingonlyallowedlist = extract_crash_scope_list(annotations, "ping-only") reportallowedlist = extract_crash_scope_list(annotations, "report") skiplist = extract_skiplist(annotations) typelist = extract_types(annotations) return { "enum": generate_enum(annotations), "strings": generate_strings(annotations), "pingallowedlist": generate_annotations_array_initializer(pingallowedlist), "pingonlyallowedlist": generate_annotations_array_initializer( pingonlyallowedlist ), "reportallowedlist": generate_annotations_array_initializer(reportallowedlist), "skiplist": generate_skiplist_initializer(skiplist), "types": generate_types_initializer(typelist), } ############################################################################### # Java/Kotlin code generation # ############################################################################### def javadoc_sanitize(s): return ( s.replace("<", "<") .replace(">", ">") .replace("@", "@") # Kotlin supports nested comments, so change anything that looks like the start of a block comment. .replace("/*", "/*") ) def derive_package_and_class(file_path): """ Determine the appropriate package and class name for a file path, and return whether a kotlin source should be generated rather than java. """ path = file_path.split("src/main/java/", 1)[1] package_path, klass_path = path.rsplit("/", 1) package = package_path.replace("/", ".") is_kotlin = klass_path.endswith(".kt") klass = klass_path.removesuffix(".kt").removesuffix(".java") return package, klass, is_kotlin @content_generator("java", "kt") def emit_java(annotations, output_name): package, klass, is_kotlin = derive_package_and_class(output_name) enum = ",\n".join( f"/** {javadoc_sanitize(data['description'])} */\n{name}(\"{name}\", \"{data.get('scope', 'client')}\")" for (name, data) in annotations ) return { "package": package, "enum": enum, "class": klass, } # Main script entrypoint for GeneratedFile def main(output, annotations_path): with open(annotations_path) as annotations_file: annotations = yaml.safe_load(annotations_file) suffix = output.name.rpartition(".")[2] generator = generators.get(suffix) if generator is None: sys.exit(f"No generator for .{suffix} files") template_file = f"CrashAnnotations.{suffix}.in" template_path = path.join(path.dirname(__file__), template_file) template_args = generator(annotations, output.name) content = template_header + string.Template( read_template(template_path) ).substitute(template_args) try: output.write(content) except OSError as ex: sys.exit("Error while writing out the generated file:\n" + str(ex) + "\n") return {template_path}