# 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 = ["boolean", "integer", "string"] 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_whitelist(annotations): """Extract an array holding the names of the annotations whitelisted for inclusion in the crash ping.""" return [ name for (name, data) in sorted(annotations.items()) if data.get("ping", False) ] ############################################################################### # 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_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_header(template, annotations): """Generate a header by filling the template with the the list of annotations and return it as a string.""" whitelist = extract_crash_ping_whitelist(annotations) return template_header + string.Template(template).substitute( { "enum": generate_enum(annotations), "strings": generate_strings(annotations), "whitelist": generate_array_initializer(whitelist), } ) 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.""" whitelist = extract_crash_ping_whitelist(annotations) return template_header + string.Template(template).substitute( { "whitelist": generate_java_array_initializer(whitelist), } ) 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_WHITELIST = { ${whitelist} }; }""" ) 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)