Source code
Revision control
Copy as Markdown
Other Tools
# 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,
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 sort_annotations(annotations):
"""Return annotations in ascending alphabetical order ignoring case"""
return sorted(annotations.items(), key=lambda annotation: str.lower(annotation[0]))
def validate_annotations(annotations):
"""Ensure that the annotations have all the required fields"""
for name, data in annotations:
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 = sort_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 annotations 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 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"
def extract_types(annotations):
"""Extract an array holding the type of each annotation."""
return [type_to_enum(data.get("type")) for (_, data) in annotations]
###############################################################################
# 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)
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)