gecko-dev/toolkit/crashreporter/generate_crash_reporter_sources.py
Gabriele Svelto 15adf94f4d Bug 1348273 - Convert crash annotations into a machine-readable list of constants; r=ted.mielczarek,njn,dholbert,mak,cpearce,mcmanus,froydnj,Dexter,jrmuizel,jchen,jimm,bz,surkov
This introduces the machinery needed to generate crash annotations from a YAML
file. The relevant C++ functions are updated to take a typed enum. JavaScript
calls are unaffected but they will throw if the string argument does not
correspond to one of the known entries in the C++ enum. The existing whitelists
and blacklists of annotations are also generated from the YAML file and all
duplicate code related to them has been consolidated. Once written out to the
.extra file the annotations are converted in string form and are no different
than the existing ones.

All existing annotations have been included in the list (and some obsolete ones
have been removed) and all call sites have been updated including tests where
appropriate.

--HG--
extra : source : 4f6c43f2830701ec5552e08e3f1b06fe6d045860
2018-07-05 15:42:11 +02:00

201 lines
6.5 KiB
Python

# 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)]
def extract_content_process_blacklist(annotations):
"""Extract an array holding the names of the annotations blacklisted when
read from a content process."""
return [name
for (name, data)
in sorted(annotations.items())
if not data.get("content", True)]
###############################################################################
# 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)
blacklist = extract_content_process_blacklist(annotations)
return template_header + string.Template(template).substitute({
"enum": generate_enum(annotations),
"strings": generate_strings(annotations),
"whitelist": generate_array_initializer(whitelist),
"blacklist": generate_array_initializer(blacklist),
})
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)