# 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 import textwrap import yaml ############################################################################### # Language-agnostic functionality # ############################################################################### template_header = ( "/* This file was autogenerated by " "toolkit/crashreporter/generate_crash_reporter_sources.py. DO NOT EDIT */\n\n" ) def validate_annotations(annotations): """Ensure that the annotations have all the required fields""" for name, data in sorted(annotations.items()): if "description" not in data: print("Annotation " + name + " does not have a description\n") sys.exit(1) if "type" not in data: print("Annotation " + name + " does not have a type\n") sys.exit(1) else: annotation_type = data.get("type") valid_types = ["string", "boolean", "u32", "u64", "usize"] if not any(annotation_type == t for t in valid_types): print( "Annotation " + name + " has an unknown type: " + annotation_type + "\n" ) sys.exit(1) def read_annotations(annotations_filename): """Read the annotations from a YAML file. If an error is encountered quit the program.""" try: with open(annotations_filename, "r") as annotations_file: annotations = yaml.safe_load(annotations_file) except (IOError, ValueError) as e: print("Error parsing " + annotations_filename + ":\n" + str(e) + "\n") sys.exit(1) validate_annotations(annotations) return annotations def read_template(template_filename): """Read the contents of the template. If an error is encountered quit the program.""" try: with open(template_filename, "r") as template_file: template = template_file.read() except IOError as ex: print("Error when reading " + template_filename + ":\n" + str(ex) + "\n") sys.exit(1) return template def extract_crash_ping_allowedlist(annotations): """Extract an array holding the names of the annotations allowed for inclusion in the crash ping.""" return [ name for (name, data) in sorted(annotations.items()) if data.get("ping", False) ] 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 sorted(annotations.items()) 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" def extract_types(annotations): """Extract an array holding the type of each annotation.""" return [type_to_enum(data.get("type")) for (_, data) in sorted(annotations.items())] ############################################################################### # C++ code generation # ############################################################################### def generate_strings(annotations): """Generate strings corresponding to every annotation.""" names = [ ' "' + data.get("altname", name) + '"' for (name, data) in sorted(annotations.items()) ] 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(sorted(annotations.items())): 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) def generate_header(template, annotations): """Generate a header by filling the template with the the list of annotations and return it as a string.""" allowedlist = extract_crash_ping_allowedlist(annotations) skiplist = extract_skiplist(annotations) typelist = extract_types(annotations) return template_header + string.Template(template).substitute( { "enum": generate_enum(annotations), "strings": generate_strings(annotations), "allowedlist": generate_annotations_array_initializer(allowedlist), "skiplist": generate_skiplist_initializer(skiplist), "types": generate_types_initializer(typelist), } ) def emit_header(output, template_filename, annotations_filename): """Generate the C++ header from the template and write it out.""" annotations = read_annotations(annotations_filename) template = read_template(template_filename) generated_header = generate_header(template, annotations) try: output.write(generated_header) except IOError as ex: print("Error while writing out the generated file:\n" + str(ex) + "\n") sys.exit(1) ############################################################################### # Java code generation # ############################################################################### def generate_java_array_initializer(contents): """Generates the initializer for an array of strings. Effectively turns `["a", "b"]` into ' \"a\",\n \"b\"\n'.""" initializer = "" for name in contents: initializer += ' "' + name + '",\n' return initializer.strip(",\n") def generate_class(template, annotations): """Fill the class template from the list of annotations.""" allowedlist = extract_crash_ping_allowedlist(annotations) return template_header + string.Template(template).substitute( { "allowedlist": generate_java_array_initializer(allowedlist), } ) def emit_class(output, annotations_filename): """Generate the CrashReporterConstants.java file.""" template = textwrap.dedent( """\ package org.mozilla.gecko; /** * Constants used by the crash reporter. These are generated so that they * are kept in sync with the other C++ and JS users. */ public class CrashReporterConstants { public static final String[] ANNOTATION_ALLOWEDLIST = { ${allowedlist} }; }""" ) annotations = read_annotations(annotations_filename) generated_class = generate_class(template, annotations) try: output.write(generated_class) except IOError as ex: print("Error while writing out the generated file:\n" + str(ex) + "\n") sys.exit(1)