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
from os import path
import yaml
###############################################################################
# Language-agnostic functionality #
###############################################################################
template_header = (
"/* This file was autogenerated by "
"toolkit/crashreporter/annotations/generate.py. DO NOT EDIT */\n\n"
)
def read_template(template_filename):
"""Read the contents of the template.
If an error is encountered quit the program."""
try:
with open(template_filename) as template_file:
template = template_file.read()
except OSError as ex:
sys.exit("Error when reading " + template_filename + ":\n" + str(ex) + "\n")
return template
def extract_crash_scope_list(annotations, scope):
"""Extract an array holding the names of the annotations allowed for
inclusion in a crash scope."""
return [
name for (name, data) in annotations if data.get("scope", "client") == scope
]
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"
elif annotation_type == "object":
return "Object"
def extract_types(annotations):
"""Extract an array holding the type of each annotation."""
return [type_to_enum(data.get("type")) for (_, data) in annotations]
generators = {}
def content_generator(*extensions):
"""Create a function that generates the content for a file extension."""
def f(func):
for e in extensions:
generators[e] = func
return f
###############################################################################
# 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)
@content_generator("h")
def emit_header(annotations, _output_name):
pingallowedlist = extract_crash_scope_list(annotations, "ping")
pingonlyallowedlist = extract_crash_scope_list(annotations, "ping-only")
reportallowedlist = extract_crash_scope_list(annotations, "report")
skiplist = extract_skiplist(annotations)
typelist = extract_types(annotations)
return {
"enum": generate_enum(annotations),
"strings": generate_strings(annotations),
"pingallowedlist": generate_annotations_array_initializer(pingallowedlist),
"pingonlyallowedlist": generate_annotations_array_initializer(
pingonlyallowedlist
),
"reportallowedlist": generate_annotations_array_initializer(reportallowedlist),
"skiplist": generate_skiplist_initializer(skiplist),
"types": generate_types_initializer(typelist),
}
###############################################################################
# Java/Kotlin code generation #
###############################################################################
def javadoc_sanitize(s):
return (
s.replace("<", "<")
.replace(">", ">")
.replace("@", "@")
# Kotlin supports nested comments, so change anything that looks like the start of a block comment.
.replace("/*", "/*")
)
def derive_package_and_class(file_path):
"""
Determine the appropriate package and class name for a file path, and
return whether a kotlin source should be generated rather than java.
"""
path = file_path.split("src/main/java/", 1)[1]
package_path, klass_path = path.rsplit("/", 1)
package = package_path.replace("/", ".")
is_kotlin = klass_path.endswith(".kt")
klass = klass_path.removesuffix(".kt").removesuffix(".java")
return package, klass, is_kotlin
@content_generator("java", "kt")
def emit_java(annotations, output_name):
package, klass, is_kotlin = derive_package_and_class(output_name)
enum = ",\n".join(
f"/** {javadoc_sanitize(data['description'])} */\n{name}(\"{name}\", \"{data.get('scope', 'client')}\")"
for (name, data) in annotations
)
return {
"package": package,
"enum": enum,
"class": klass,
}
# Main script entrypoint for GeneratedFile
def main(output, annotations_path):
with open(annotations_path) as annotations_file:
annotations = yaml.safe_load(annotations_file)
suffix = output.name.rpartition(".")[2]
generator = generators.get(suffix)
if generator is None:
sys.exit(f"No generator for .{suffix} files")
template_file = f"CrashAnnotations.{suffix}.in"
template_path = path.join(path.dirname(__file__), template_file)
template_args = generator(annotations, output.name)
content = template_header + string.Template(
read_template(template_path)
).substitute(template_args)
try:
output.write(content)
except OSError as ex:
sys.exit("Error while writing out the generated file:\n" + str(ex) + "\n")
return {template_path}