1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
|
# 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
from os import path
import yaml
###############################################################################
# Language-agnostic functionality #
###############################################################################
annotations_filename = path.join(path.dirname(__file__), "CrashAnnotations.yaml")
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)
annotation_type = data.get("type")
valid_types = ["string", "boolean", "u32", "u64", "usize", "object"]
if annotation_type not in valid_types:
print(
"Annotation " + name + " has an unknown type: " + annotation_type + "\n"
)
sys.exit(1)
annotation_scope = data.get("scope", "client")
valid_scopes = ["client", "report", "ping"]
if annotation_scope not in valid_scopes:
print(
"Annotation "
+ name
+ " has an unknown scope: "
+ annotation_scope
+ "\n"
)
sys.exit(1)
def read_annotations():
"""Read the annotations from a YAML file.
If an error is encountered quit the program."""
try:
with open(annotations_filename) as annotations_file:
annotations = sort_annotations(yaml.safe_load(annotations_file))
except (OSError, 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) as template_file:
template = template_file.read()
except OSError 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 a crash ping."""
return [
name for (name, data) in annotations if data.get("scope", "client") == "ping"
]
def extract_crash_report_allowedlist(annotations):
"""Extract an array holding the names of the annotations allowed for
inclusion in a crash report (excluding those allowed for pings)."""
return [
name for (name, data) in annotations if data.get("scope", "client") == "report"
]
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]
###############################################################################
# C++ code generation #
###############################################################################
header_template_filename = path.join(path.dirname(__file__), "CrashAnnotations.h.in")
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."""
pingallowedlist = extract_crash_ping_allowedlist(annotations)
reportallowedlist = extract_crash_report_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),
"pingallowedlist": generate_annotations_array_initializer(pingallowedlist),
"reportallowedlist": generate_annotations_array_initializer(
reportallowedlist
),
"skiplist": generate_skiplist_initializer(skiplist),
"types": generate_types_initializer(typelist),
}
)
def emit_header(output):
"""Generate the C++ header from the template and write it out."""
annotations = read_annotations()
template = read_template(header_template_filename)
generated_header = generate_header(template, annotations)
try:
output.write(generated_header)
except OSError as ex:
print("Error while writing out the generated file:\n" + str(ex) + "\n")
sys.exit(1)
return {annotations_filename, header_template_filename}
###############################################################################
# Java 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 generate_class(template, package, klass, annotations):
"""Fill the class template from the list of annotations."""
enum = ",\n".join(
f"/** {javadoc_sanitize(data['description'])} */\n{name}(\"{name}\", \"{data.get('scope', 'client')}\")"
for (name, data) in annotations
)
return template_header + string.Template(template).substitute(
{
"package": package,
"enum": enum,
"class": klass,
}
)
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
def emit_java(output):
"""Generate the CrashReporter Java/Kotlin file."""
package, klass, is_kotlin = derive_package_and_class(output.name)
java_template = textwrap.dedent(
"""\
package ${package};
import androidx.annotation.AnyThread;
/**
* Constants used by the crash reporter. These are generated so that they
* are kept in sync with the other C++ and JS users.
*/
final class ${class} {
/** Crash Annotations */
@AnyThread
public enum Annotation {
${enum};
private final String s;
private final String scope;
private Annotation(String s, String scope) {
this.s = s;
this.scope = scope;
}
public String toString() {
return this.s;
}
/** @return Whether the annotation should be included in crash pings. */
public boolean allowedInPing() {
return this.scope.equals("ping");
}
/** @return Whether the annotation should be included in crash reports. */
public boolean allowedInReport() {
return allowedInPing() || this.scope.equals("report");
}
}
}
"""
)
kotlin_template = textwrap.dedent(
"""\
package ${package}
import androidx.annotation.AnyThread
/**
* Constants used by the crash reporter. These are generated so that they
* are kept in sync with the other C++ and JS users.
*/
internal class ${class} {
/** Crash Annotations */
@AnyThread
enum class Annotation private constructor (private val s: String, private val scope: String) {
${enum};
public override fun toString() = s
/** @return Whether the annotation should be included in crash pings. */
public fun allowedInPing() = scope.equals("ping")
/** @return Whether the annotation should be included in crash reports. */
public fun allowedInReport() = allowedInPing() || scope.equals("report")
}
}
"""
)
template = kotlin_template if is_kotlin else java_template
annotations = read_annotations()
generated_class = generate_class(template, package, klass, annotations)
try:
output.write(generated_class)
except OSError as ex:
print("Error while writing out the generated file:\n" + str(ex) + "\n")
sys.exit(1)
return {annotations_filename}
|