summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/generate_crash_reporter_sources.py
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/generate_crash_reporter_sources.py')
-rw-r--r--toolkit/crashreporter/generate_crash_reporter_sources.py259
1 files changed, 259 insertions, 0 deletions
diff --git a/toolkit/crashreporter/generate_crash_reporter_sources.py b/toolkit/crashreporter/generate_crash_reporter_sources.py
new file mode 100644
index 0000000000..b8c3333557
--- /dev/null
+++ b/toolkit/crashreporter/generate_crash_reporter_sources.py
@@ -0,0 +1,259 @@
+# 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)