1# 2# Copyright (C) 2012 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17""" 18A set of helpers for rendering Mako templates with a Metadata model. 19""" 20 21import metadata_model 22import re 23import markdown 24import textwrap 25import sys 26import bs4 27# Monkey-patch BS4. WBR element must not have an end tag. 28bs4.builder.HTMLTreeBuilder.empty_element_tags.add("wbr") 29 30from collections import OrderedDict 31 32# Relative path from HTML file to the base directory used by <img> tags 33IMAGE_SRC_METADATA="images/camera2/metadata/" 34 35# Prepend this path to each <img src="foo"> in javadocs 36JAVADOC_IMAGE_SRC_METADATA="/reference/" + IMAGE_SRC_METADATA 37NDKDOC_IMAGE_SRC_METADATA="../" + IMAGE_SRC_METADATA 38 39_context_buf = None 40_hal_major_version = None 41_hal_minor_version = None 42 43def _is_sec_or_ins(x): 44 return isinstance(x, metadata_model.Section) or \ 45 isinstance(x, metadata_model.InnerNamespace) 46 47## 48## Metadata Helpers 49## 50 51def find_all_sections(root): 52 """ 53 Find all descendants that are Section or InnerNamespace instances. 54 55 Args: 56 root: a Metadata instance 57 58 Returns: 59 A list of Section/InnerNamespace instances 60 61 Remarks: 62 These are known as "sections" in the generated C code. 63 """ 64 return root.find_all(_is_sec_or_ins) 65 66def find_parent_section(entry): 67 """ 68 Find the closest ancestor that is either a Section or InnerNamespace. 69 70 Args: 71 entry: an Entry or Clone node 72 73 Returns: 74 An instance of Section or InnerNamespace 75 """ 76 return entry.find_parent_first(_is_sec_or_ins) 77 78# find uniquely named entries (w/o recursing through inner namespaces) 79def find_unique_entries(node): 80 """ 81 Find all uniquely named entries, without recursing through inner namespaces. 82 83 Args: 84 node: a Section or InnerNamespace instance 85 86 Yields: 87 A sequence of MergedEntry nodes representing an entry 88 89 Remarks: 90 This collapses multiple entries with the same fully qualified name into 91 one entry (e.g. if there are multiple entries in different kinds). 92 """ 93 if not isinstance(node, metadata_model.Section) and \ 94 not isinstance(node, metadata_model.InnerNamespace): 95 raise TypeError("expected node to be a Section or InnerNamespace") 96 97 d = OrderedDict() 98 # remove the 'kinds' from the path between sec and the closest entries 99 # then search the immediate children of the search path 100 search_path = isinstance(node, metadata_model.Section) and node.kinds \ 101 or [node] 102 for i in search_path: 103 for entry in i.entries: 104 d[entry.name] = entry 105 106 for k,v in d.items(): 107 yield v.merge() 108 109def path_name(node): 110 """ 111 Calculate a period-separated string path from the root to this element, 112 by joining the names of each node and excluding the Metadata/Kind nodes 113 from the path. 114 115 Args: 116 node: a Node instance 117 118 Returns: 119 A string path 120 """ 121 122 isa = lambda x,y: isinstance(x, y) 123 fltr = lambda x: not isa(x, metadata_model.Metadata) and \ 124 not isa(x, metadata_model.Kind) 125 126 path = node.find_parents(fltr) 127 path = list(path) 128 path.reverse() 129 path.append(node) 130 131 return ".".join((i.name for i in path)) 132 133def ndk(name): 134 """ 135 Return the NDK version of given name, which replace 136 the leading "android" to "acamera" 137 138 Args: 139 name: name string of an entry 140 141 Returns: 142 A NDK version name string of the input name 143 """ 144 name_list = name.split(".") 145 if name_list[0] == "android": 146 name_list[0] = "acamera" 147 return ".".join(name_list) 148 149def protobuf_type(entry): 150 """ 151 Return the protocol buffer message type for input metadata entry. 152 Only support types used by static metadata right now 153 154 Returns: 155 A string of protocol buffer type. Ex: "optional int32" or "repeated RangeInt" 156 """ 157 typeName = None 158 if entry.typedef is None: 159 typeName = entry.type 160 else: 161 typeName = entry.typedef.name 162 163 typename_to_protobuftype = { 164 "rational" : "Rational", 165 "size" : "Size", 166 "sizeF" : "SizeF", 167 "rectangle" : "Rect", 168 "streamConfigurationMap" : "StreamConfigurations", 169 "mandatoryStreamCombination" : "MandatoryStreamCombination", 170 "rangeInt" : "RangeInt", 171 "rangeLong" : "RangeLong", 172 "rangeFloat" : "RangeFloat", 173 "colorSpaceTransform" : "ColorSpaceTransform", 174 "blackLevelPattern" : "BlackLevelPattern", 175 "byte" : "int32", # protocol buffer don't support byte 176 "boolean" : "bool", 177 "float" : "float", 178 "double" : "double", 179 "int32" : "int32", 180 "int64" : "int64", 181 "enumList" : "int32", 182 "string" : "string", 183 "capability" : "Capability", 184 "multiResolutionStreamConfigurationMap" : "MultiResolutionStreamConfigurations" 185 } 186 187 if typeName not in typename_to_protobuftype: 188 print(" ERROR: Could not find protocol buffer type for {%s} type {%s} typedef {%s}" % \ 189 (entry.name, entry.type, entry.typedef), file=sys.stderr) 190 191 proto_type = typename_to_protobuftype[typeName] 192 193 prefix = "optional" 194 if entry.container == 'array': 195 has_variable_size = False 196 for size in entry.container_sizes: 197 try: 198 size_int = int(size) 199 except ValueError: 200 has_variable_size = True 201 202 if has_variable_size: 203 prefix = "repeated" 204 205 return "%s %s" %(prefix, proto_type) 206 207 208def protobuf_name(entry): 209 """ 210 Return the protocol buffer field name for input metadata entry 211 212 Returns: 213 A string. Ex: "android_colorCorrection_availableAberrationModes" 214 """ 215 return entry.name.replace(".", "_") 216 217def has_descendants_with_enums(node): 218 """ 219 Determine whether or not the current node is or has any descendants with an 220 Enum node. 221 222 Args: 223 node: a Node instance 224 225 Returns: 226 True if it finds an Enum node in the subtree, False otherwise 227 """ 228 return bool(node.find_first(lambda x: isinstance(x, metadata_model.Enum))) 229 230def get_children_by_throwing_away_kind(node, member='entries'): 231 """ 232 Get the children of this node by compressing the subtree together by removing 233 the kind and then combining any children nodes with the same name together. 234 235 Args: 236 node: An instance of Section, InnerNamespace, or Kind 237 238 Returns: 239 An iterable over the combined children of the subtree of node, 240 as if the Kinds never existed. 241 242 Remarks: 243 Not recursive. Call this function repeatedly on each child. 244 """ 245 246 if isinstance(node, metadata_model.Section): 247 # Note that this makes jump from Section to Kind, 248 # skipping the Kind entirely in the tree. 249 node_to_combine = node.combine_kinds_into_single_node() 250 else: 251 node_to_combine = node 252 253 combined_kind = node_to_combine.combine_children_by_name() 254 255 return (i for i in getattr(combined_kind, member)) 256 257def get_children_by_filtering_kind(section, kind_name, member='entries'): 258 """ 259 Takes a section and yields the children of the merged kind under this section. 260 261 Args: 262 section: An instance of Section 263 kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls' 264 265 Returns: 266 An iterable over the children of the specified merged kind. 267 """ 268 269 matched_kind = next((i for i in section.merged_kinds if i.name == kind_name), None) 270 271 if matched_kind: 272 return getattr(matched_kind, member) 273 else: 274 return () 275 276## 277## Filters 278## 279 280# abcDef.xyz -> ABC_DEF_XYZ 281def csym(name): 282 """ 283 Convert an entry name string into an uppercase C symbol. 284 285 Returns: 286 A string 287 288 Example: 289 csym('abcDef.xyz') == 'ABC_DEF_XYZ' 290 """ 291 newstr = name 292 newstr = "".join([i.isupper() and ("_" + i) or i for i in newstr]).upper() 293 newstr = newstr.replace(".", "_") 294 return newstr 295 296# abcDef.xyz -> abc_def_xyz 297def csyml(name): 298 """ 299 Convert an entry name string into a lowercase C symbol. 300 301 Returns: 302 A string 303 304 Example: 305 csyml('abcDef.xyz') == 'abc_def_xyz' 306 """ 307 return csym(name).lower() 308 309# pad with spaces to make string len == size. add new line if too big 310def ljust(size, indent=4): 311 """ 312 Creates a function that given a string will pad it with spaces to make 313 the string length == size. Adds a new line if the string was too big. 314 315 Args: 316 size: an integer representing how much spacing should be added 317 indent: an integer representing the initial indendation level 318 319 Returns: 320 A function that takes a string and returns a string. 321 322 Example: 323 ljust(8)("hello") == 'hello ' 324 325 Remarks: 326 Deprecated. Use pad instead since it works for non-first items in a 327 Mako template. 328 """ 329 def inner(what): 330 newstr = what.ljust(size) 331 if len(newstr) > size: 332 return what + "\n" + "".ljust(indent + size) 333 else: 334 return newstr 335 return inner 336 337def _find_new_line(): 338 339 if _context_buf is None: 340 raise ValueError("Context buffer was not set") 341 342 buf = _context_buf 343 x = -1 # since the first read is always '' 344 cur_pos = buf.tell() 345 while buf.tell() > 0 and buf.read(1) != '\n': 346 buf.seek(cur_pos - x) 347 x = x + 1 348 349 buf.seek(cur_pos) 350 351 return int(x) 352 353# Pad the string until the buffer reaches the desired column. 354# If string is too long, insert a new line with 'col' spaces instead 355def pad(col): 356 """ 357 Create a function that given a string will pad it to the specified column col. 358 If the string overflows the column, put the string on a new line and pad it. 359 360 Args: 361 col: an integer specifying the column number 362 363 Returns: 364 A function that given a string will produce a padded string. 365 366 Example: 367 pad(8)("hello") == 'hello ' 368 369 Remarks: 370 This keeps track of the line written by Mako so far, so it will always 371 align to the column number correctly. 372 """ 373 def inner(what): 374 wut = int(col) 375 current_col = _find_new_line() 376 377 if len(what) > wut - current_col: 378 return what + "\n".ljust(col) 379 else: 380 return what.ljust(wut - current_col) 381 return inner 382 383# int32 -> TYPE_INT32, byte -> TYPE_BYTE, etc. note that enum -> TYPE_INT32 384def ctype_enum(what): 385 """ 386 Generate a camera_metadata_type_t symbol from a type string. 387 388 Args: 389 what: a type string 390 391 Returns: 392 A string representing the camera_metadata_type_t 393 394 Example: 395 ctype_enum('int32') == 'TYPE_INT32' 396 ctype_enum('int64') == 'TYPE_INT64' 397 ctype_enum('float') == 'TYPE_FLOAT' 398 399 Remarks: 400 An enum is coerced to a byte since the rest of the camera_metadata 401 code doesn't support enums directly yet. 402 """ 403 return 'TYPE_%s' %(what.upper()) 404 405 406# Calculate a java type name from an entry with a Typedef node 407def _jtypedef_type(entry): 408 typedef = entry.typedef 409 additional = '' 410 411 # Hacky way to deal with arrays. Assume that if we have 412 # size 'Constant x N' the Constant is part of the Typedef size. 413 # So something sized just 'Constant', 'Constant1 x Constant2', etc 414 # is not treated as a real java array. 415 if entry.container == 'array': 416 has_variable_size = False 417 for size in entry.container_sizes: 418 try: 419 size_int = int(size) 420 except ValueError: 421 has_variable_size = True 422 423 if has_variable_size: 424 additional = '[]' 425 426 try: 427 name = typedef.languages['java'] 428 429 return "%s%s" %(name, additional) 430 except KeyError: 431 return None 432 433# Box if primitive. Otherwise leave unboxed. 434def _jtype_box(type_name): 435 mapping = { 436 'boolean': 'Boolean', 437 'byte': 'Byte', 438 'int': 'Integer', 439 'float': 'Float', 440 'double': 'Double', 441 'long': 'Long' 442 } 443 444 return mapping.get(type_name, type_name) 445 446def jtype_unboxed(entry): 447 """ 448 Calculate the Java type from an entry type string, to be used whenever we 449 need the regular type in Java. It's not boxed, so it can't be used as a 450 generic type argument when the entry type happens to resolve to a primitive. 451 452 Remarks: 453 Since Java generics cannot be instantiated with primitives, this version 454 is not applicable in that case. Use jtype_boxed instead for that. 455 456 Returns: 457 The string representing the Java type. 458 """ 459 if not isinstance(entry, metadata_model.Entry): 460 raise ValueError("Expected entry to be an instance of Entry") 461 462 metadata_type = entry.type 463 464 java_type = None 465 466 if entry.typedef: 467 typedef_name = _jtypedef_type(entry) 468 if typedef_name: 469 java_type = typedef_name # already takes into account arrays 470 471 if not java_type: 472 if not java_type and entry.enum and metadata_type == 'byte': 473 # Always map byte enums to Java ints, unless there's a typedef override 474 base_type = 'int' 475 476 else: 477 mapping = { 478 'int32': 'int', 479 'int64': 'long', 480 'float': 'float', 481 'double': 'double', 482 'byte': 'byte', 483 'rational': 'Rational' 484 } 485 486 base_type = mapping[metadata_type] 487 488 # Convert to array (enums, basic types) 489 if entry.container == 'array': 490 additional = '[]' 491 else: 492 additional = '' 493 494 java_type = '%s%s' %(base_type, additional) 495 496 # Now box this sucker. 497 return java_type 498 499def jtype_boxed(entry): 500 """ 501 Calculate the Java type from an entry type string, to be used as a generic 502 type argument in Java. The type is guaranteed to inherit from Object. 503 504 It will only box when absolutely necessary, i.e. int -> Integer[], but 505 int[] -> int[]. 506 507 Remarks: 508 Since Java generics cannot be instantiated with primitives, this version 509 will use boxed types when absolutely required. 510 511 Returns: 512 The string representing the boxed Java type. 513 """ 514 unboxed_type = jtype_unboxed(entry) 515 return _jtype_box(unboxed_type) 516 517def _is_jtype_generic(entry): 518 """ 519 Determine whether or not the Java type represented by the entry type 520 string and/or typedef is a Java generic. 521 522 For example, "Range<Integer>" would be considered a generic, whereas 523 a "MeteringRectangle" or a plain "Integer" would not be considered a generic. 524 525 Args: 526 entry: An instance of an Entry node 527 528 Returns: 529 True if it's a java generic, False otherwise. 530 """ 531 if entry.typedef: 532 local_typedef = _jtypedef_type(entry) 533 if local_typedef: 534 match = re.search(r'<.*>', local_typedef) 535 return bool(match) 536 return False 537 538def _jtype_primitive(what): 539 """ 540 Calculate the Java type from an entry type string. 541 542 Remarks: 543 Makes a special exception for Rational, since it's a primitive in terms of 544 the C-library camera_metadata type system. 545 546 Returns: 547 The string representing the primitive type 548 """ 549 mapping = { 550 'int32': 'int', 551 'int64': 'long', 552 'float': 'float', 553 'double': 'double', 554 'byte': 'byte', 555 'rational': 'Rational' 556 } 557 558 try: 559 return mapping[what] 560 except KeyError as e: 561 raise ValueError("Can't map '%s' to a primitive, not supported" %what) 562 563def jclass(entry): 564 """ 565 Calculate the java Class reference string for an entry. 566 567 Args: 568 entry: an Entry node 569 570 Example: 571 <entry name="some_int" type="int32"/> 572 <entry name="some_int_array" type="int32" container='array'/> 573 574 jclass(some_int) == 'int.class' 575 jclass(some_int_array) == 'int[].class' 576 577 Returns: 578 The ClassName.class string 579 """ 580 581 return "%s.class" %jtype_unboxed(entry) 582 583def jkey_type_token(entry): 584 """ 585 Calculate the java type token compatible with a Key constructor. 586 This will be the Java Class<T> for non-generic classes, and a 587 TypeReference<T> for generic classes. 588 589 Args: 590 entry: An entry node 591 592 Returns: 593 The ClassName.class string, or 'new TypeReference<ClassName>() {{ }}' string 594 """ 595 if _is_jtype_generic(entry): 596 return "new TypeReference<%s>() {{ }}" %(jtype_boxed(entry)) 597 else: 598 return jclass(entry) 599 600def jidentifier(what): 601 """ 602 Convert the input string into a valid Java identifier. 603 604 Args: 605 what: any identifier string 606 607 Returns: 608 String with added underscores if necessary. 609 """ 610 if re.match("\d", what): 611 return "_%s" %what 612 else: 613 return what 614 615def enum_calculate_value_string(enum_value): 616 """ 617 Calculate the value of the enum, even if it does not have one explicitly 618 defined. 619 620 This looks back for the first enum value that has a predefined value and then 621 applies addition until we get the right value, using C-enum semantics. 622 623 Args: 624 enum_value: an EnumValue node with a valid Enum parent 625 626 Example: 627 <enum> 628 <value>X</value> 629 <value id="5">Y</value> 630 <value>Z</value> 631 </enum> 632 633 enum_calculate_value_string(X) == '0' 634 enum_calculate_Value_string(Y) == '5' 635 enum_calculate_value_string(Z) == '6' 636 637 Returns: 638 String that represents the enum value as an integer literal. 639 """ 640 641 enum_value_siblings = list(enum_value.parent.values) 642 this_index = enum_value_siblings.index(enum_value) 643 644 def is_hex_string(instr): 645 return bool(re.match('0x[a-f0-9]+$', instr, re.IGNORECASE)) 646 647 base_value = 0 648 base_offset = 0 649 emit_as_hex = False 650 651 this_id = enum_value_siblings[this_index].id 652 while this_index != 0 and not this_id: 653 this_index -= 1 654 base_offset += 1 655 this_id = enum_value_siblings[this_index].id 656 657 if this_id: 658 base_value = int(this_id, 0) # guess base 659 emit_as_hex = is_hex_string(this_id) 660 661 if emit_as_hex: 662 return "0x%X" %(base_value + base_offset) 663 else: 664 return "%d" %(base_value + base_offset) 665 666def enumerate_with_last(iterable): 667 """ 668 Enumerate a sequence of iterable, while knowing if this element is the last in 669 the sequence or not. 670 671 Args: 672 iterable: an Iterable of some sequence 673 674 Yields: 675 (element, bool) where the bool is True iff the element is last in the seq. 676 """ 677 it = (i for i in iterable) 678 679 try: 680 first = next(it) # OK: raises exception if it is empty 681 except StopIteration: 682 return 683 684 second = first # for when we have only 1 element in iterable 685 686 try: 687 while True: 688 second = next(it) 689 # more elements remaining. 690 yield (first, False) 691 first = second 692 except StopIteration: 693 # last element. no more elements left 694 yield (second, True) 695 696def pascal_case(what): 697 """ 698 Convert the first letter of a string to uppercase, to make the identifier 699 conform to PascalCase. 700 701 If there are dots, remove the dots, and capitalize the letter following 702 where the dot was. Letters that weren't following dots are left unchanged, 703 except for the first letter of the string (which is made upper-case). 704 705 Args: 706 what: a string representing some identifier 707 708 Returns: 709 String with first letter capitalized 710 711 Example: 712 pascal_case("helloWorld") == "HelloWorld" 713 pascal_case("foo") == "Foo" 714 pascal_case("hello.world") = "HelloWorld" 715 pascal_case("fooBar.fooBar") = "FooBarFooBar" 716 """ 717 return "".join([s[0:1].upper() + s[1:] for s in what.split('.')]) 718 719def jkey_identifier(what): 720 """ 721 Return a Java identifier from a property name. 722 723 Args: 724 what: a string representing a property name. 725 726 Returns: 727 Java identifier corresponding to the property name. May need to be 728 prepended with the appropriate Java class name by the caller of this 729 function. Note that the outer namespace is stripped from the property 730 name. 731 732 Example: 733 jkey_identifier("android.lens.facing") == "LENS_FACING" 734 """ 735 return csym(what[what.find('.') + 1:]) 736 737def jenum_value(enum_entry, enum_value): 738 """ 739 Calculate the Java name for an integer enum value 740 741 Args: 742 enum: An enum-typed Entry node 743 value: An EnumValue node for the enum 744 745 Returns: 746 String representing the Java symbol 747 """ 748 749 cname = csym(enum_entry.name) 750 return cname[cname.find('_') + 1:] + '_' + enum_value.name 751 752def generate_extra_javadoc_detail(entry): 753 """ 754 Returns a function to add extra details for an entry into a string for inclusion into 755 javadoc. Adds information about units, the list of enum values for this key, and the valid 756 range. 757 """ 758 def inner(text): 759 if entry.units and not (entry.typedef and entry.typedef.name == 'string'): 760 text += '\n\n<b>Units</b>: %s\n' % (dedent(entry.units)) 761 if entry.enum and not (entry.typedef and entry.typedef.languages.get('java')): 762 text += '\n\n<b>Possible values:</b>\n<ul>\n' 763 for value in entry.enum.values: 764 if not value.hidden: 765 text += ' <li>{@link #%s %s}</li>\n' % ( jenum_value(entry, value ), value.name ) 766 text += '</ul>\n' 767 if entry.range: 768 if entry.enum and not (entry.typedef and entry.typedef.languages.get('java')): 769 text += '\n\n<b>Available values for this device:</b><br>\n' 770 else: 771 text += '\n\n<b>Range of valid values:</b><br>\n' 772 text += '%s\n' % (dedent(entry.range)) 773 if entry.hwlevel != 'legacy': # covers any of (None, 'limited', 'full') 774 text += '\n\n<b>Optional</b> - The value for this key may be {@code null} on some devices.\n' 775 if entry.hwlevel == 'full': 776 text += \ 777 '\n<b>Full capability</b> - \n' + \ 778 'Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the\n' + \ 779 'android.info.supportedHardwareLevel key\n' 780 if entry.hwlevel == 'limited': 781 text += \ 782 '\n<b>Limited capability</b> - \n' + \ 783 'Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the\n' + \ 784 'android.info.supportedHardwareLevel key\n' 785 if entry.hwlevel == 'legacy': 786 text += "\nThis key is available on all devices." 787 if entry.permission_needed == "true": 788 text += "\n\n<b>Permission {@link android.Manifest.permission#CAMERA} is needed to access this property</b>\n\n" 789 790 return text 791 return inner 792 793 794def javadoc(metadata, indent = 4): 795 """ 796 Returns a function to format a markdown syntax text block as a 797 javadoc comment section, given a set of metadata 798 799 Args: 800 metadata: A Metadata instance, representing the top-level root 801 of the metadata for cross-referencing 802 indent: baseline level of indentation for javadoc block 803 Returns: 804 A function that transforms a String text block as follows: 805 - Indent and * for insertion into a Javadoc comment block 806 - Trailing whitespace removed 807 - Entire body rendered via markdown to generate HTML 808 - All tag names converted to appropriate Javadoc {@link} with @see 809 for each tag 810 811 Example: 812 "This is a comment for Javadoc\n" + 813 " with multiple lines, that should be \n" + 814 " formatted better\n" + 815 "\n" + 816 " That covers multiple lines as well\n" 817 " And references android.control.mode\n" 818 819 transforms to 820 " * <p>This is a comment for Javadoc\n" + 821 " * with multiple lines, that should be\n" + 822 " * formatted better</p>\n" + 823 " * <p>That covers multiple lines as well</p>\n" + 824 " * and references {@link CaptureRequest#CONTROL_MODE android.control.mode}\n" + 825 " *\n" + 826 " * @see CaptureRequest#CONTROL_MODE\n" 827 """ 828 def javadoc_formatter(text): 829 comment_prefix = " " * indent + " * " 830 831 # render with markdown => HTML 832 javatext = md(text, JAVADOC_IMAGE_SRC_METADATA) 833 834 # Identity transform for javadoc links 835 def javadoc_link_filter(target, target_ndk, shortname): 836 return '{@link %s %s}' % (target, shortname) 837 838 javatext = filter_links(javatext, javadoc_link_filter) 839 840 # Crossref tag names 841 kind_mapping = { 842 'static': 'CameraCharacteristics', 843 'dynamic': 'CaptureResult', 844 'controls': 'CaptureRequest' } 845 846 # Convert metadata entry "android.x.y.z" to form 847 # "{@link CaptureRequest#X_Y_Z android.x.y.z}" 848 def javadoc_crossref_filter(node): 849 if node.applied_visibility in ('public', 'java_public'): 850 return '{@link %s#%s %s}' % (kind_mapping[node.kind], 851 jkey_identifier(node.name), 852 node.name) 853 else: 854 return node.name 855 856 # For each public tag "android.x.y.z" referenced, add a 857 # "@see CaptureRequest#X_Y_Z" 858 def javadoc_crossref_see_filter(node_set): 859 node_set = (x for x in node_set if x.applied_visibility in ('public', 'java_public')) 860 861 text = '\n' 862 for node in node_set: 863 text = text + '\n@see %s#%s' % (kind_mapping[node.kind], 864 jkey_identifier(node.name)) 865 866 return text if text != '\n' else '' 867 868 javatext = filter_tags(javatext, metadata, javadoc_crossref_filter, javadoc_crossref_see_filter) 869 870 def line_filter(line): 871 # Indent each line 872 # Add ' * ' to it for stylistic reasons 873 # Strip right side of trailing whitespace 874 return (comment_prefix + line).rstrip() 875 876 # Process each line with above filter 877 javatext = "\n".join(line_filter(i) for i in javatext.split("\n")) + "\n" 878 879 return javatext 880 881 return javadoc_formatter 882 883def ndkdoc(metadata, indent = 4): 884 """ 885 Returns a function to format a markdown syntax text block as a 886 NDK camera API C/C++ comment section, given a set of metadata 887 888 Args: 889 metadata: A Metadata instance, representing the top-level root 890 of the metadata for cross-referencing 891 indent: baseline level of indentation for comment block 892 Returns: 893 A function that transforms a String text block as follows: 894 - Indent and * for insertion into a comment block 895 - Trailing whitespace removed 896 - Entire body rendered via markdown 897 - All tag names converted to appropriate NDK tag name for each tag 898 899 Example: 900 "This is a comment for NDK\n" + 901 " with multiple lines, that should be \n" + 902 " formatted better\n" + 903 "\n" + 904 " That covers multiple lines as well\n" 905 " And references android.control.mode\n" 906 907 transforms to 908 " * This is a comment for NDK\n" + 909 " * with multiple lines, that should be\n" + 910 " * formatted better\n" + 911 " * That covers multiple lines as well\n" + 912 " * and references ACAMERA_CONTROL_MODE\n" + 913 " *\n" + 914 " * @see ACAMERA_CONTROL_MODE\n" 915 """ 916 def ndkdoc_formatter(text): 917 # render with markdown => HTML 918 # Turn off the table plugin since doxygen doesn't recognize generated <thead> <tbody> tags 919 ndktext = md(text, NDKDOC_IMAGE_SRC_METADATA, False) 920 921 # Simple transform for ndk doc links 922 def ndkdoc_link_filter(target, target_ndk, shortname): 923 if target_ndk is not None: 924 return '{@link %s %s}' % (target_ndk, shortname) 925 926 # Create HTML link to Javadoc 927 if shortname == '': 928 lastdot = target.rfind('.') 929 if lastdot == -1: 930 shortname = target 931 else: 932 shortname = target[lastdot + 1:] 933 934 target = target.replace('.','/') 935 if target.find('#') != -1: 936 target = target.replace('#','.html#') 937 else: 938 target = target + '.html' 939 940 return '<a href="https://developer.android.com/reference/%s">%s</a>' % (target, shortname) 941 942 ndktext = filter_links(ndktext, ndkdoc_link_filter) 943 944 # Convert metadata entry "android.x.y.z" to form 945 # NDK tag format of "ACAMERA_X_Y_Z" 946 def ndkdoc_crossref_filter(node): 947 if node.applied_ndk_visible == 'true': 948 return csym(ndk(node.name)) 949 else: 950 return node.name 951 952 # For each public tag "android.x.y.z" referenced, add a 953 # "@see ACAMERA_X_Y_Z" 954 def ndkdoc_crossref_see_filter(node_set): 955 node_set = (x for x in node_set if x.applied_ndk_visible == 'true') 956 957 text = '\n' 958 for node in node_set: 959 text = text + '\n@see %s' % (csym(ndk(node.name))) 960 961 return text if text != '\n' else '' 962 963 ndktext = filter_tags(ndktext, metadata, ndkdoc_crossref_filter, ndkdoc_crossref_see_filter) 964 965 ndktext = ndk_replace_tag_wildcards(ndktext, metadata) 966 967 comment_prefix = " " * indent + " * "; 968 969 def line_filter(line): 970 # Indent each line 971 # Add ' * ' to it for stylistic reasons 972 # Strip right side of trailing whitespace 973 return (comment_prefix + line).rstrip() 974 975 # Process each line with above filter 976 ndktext = "\n".join(line_filter(i) for i in ndktext.split("\n")) + "\n" 977 978 return ndktext 979 980 return ndkdoc_formatter 981 982def hidldoc(metadata, indent = 4): 983 """ 984 Returns a function to format a markdown syntax text block as a 985 HIDL camera HAL module C/C++ comment section, given a set of metadata 986 987 Args: 988 metadata: A Metadata instance, representing the top-level root 989 of the metadata for cross-referencing 990 indent: baseline level of indentation for comment block 991 Returns: 992 A function that transforms a String text block as follows: 993 - Indent and * for insertion into a comment block 994 - Trailing whitespace removed 995 - Entire body rendered via markdown 996 - All tag names converted to appropriate HIDL tag name for each tag 997 998 Example: 999 "This is a comment for NDK\n" + 1000 " with multiple lines, that should be \n" + 1001 " formatted better\n" + 1002 "\n" + 1003 " That covers multiple lines as well\n" 1004 " And references android.control.mode\n" 1005 1006 transforms to 1007 " * This is a comment for NDK\n" + 1008 " * with multiple lines, that should be\n" + 1009 " * formatted better\n" + 1010 " * That covers multiple lines as well\n" + 1011 " * and references ANDROID_CONTROL_MODE\n" + 1012 " *\n" + 1013 " * @see ANDROID_CONTROL_MODE\n" 1014 """ 1015 def hidldoc_formatter(text): 1016 # render with markdown => HTML 1017 # Turn off the table plugin since doxygen doesn't recognize generated <thead> <tbody> tags 1018 hidltext = md(text, NDKDOC_IMAGE_SRC_METADATA, False) 1019 1020 # Simple transform for hidl doc links 1021 def hidldoc_link_filter(target, target_ndk, shortname): 1022 if target_ndk is not None: 1023 return '{@link %s %s}' % (target_ndk, shortname) 1024 1025 # Create HTML link to Javadoc 1026 if shortname == '': 1027 lastdot = target.rfind('.') 1028 if lastdot == -1: 1029 shortname = target 1030 else: 1031 shortname = target[lastdot + 1:] 1032 1033 target = target.replace('.','/') 1034 if target.find('#') != -1: 1035 target = target.replace('#','.html#') 1036 else: 1037 target = target + '.html' 1038 1039 return '<a href="https://developer.android.com/reference/%s">%s</a>' % (target, shortname) 1040 1041 hidltext = filter_links(hidltext, hidldoc_link_filter) 1042 1043 # Convert metadata entry "android.x.y.z" to form 1044 # HIDL tag format of "ANDROID_X_Y_Z" 1045 def hidldoc_crossref_filter(node): 1046 return csym(node.name) 1047 1048 # For each public tag "android.x.y.z" referenced, add a 1049 # "@see ANDROID_X_Y_Z" 1050 def hidldoc_crossref_see_filter(node_set): 1051 text = '\n' 1052 for node in node_set: 1053 text = text + '\n@see %s' % (csym(node.name)) 1054 1055 return text if text != '\n' else '' 1056 1057 hidltext = filter_tags(hidltext, metadata, hidldoc_crossref_filter, hidldoc_crossref_see_filter) 1058 1059 comment_prefix = " " * indent + " * "; 1060 1061 def line_filter(line): 1062 # Indent each line 1063 # Add ' * ' to it for stylistic reasons 1064 # Strip right side of trailing whitespace 1065 return (comment_prefix + line).rstrip() 1066 1067 # Process each line with above filter 1068 hidltext = "\n".join(line_filter(i) for i in hidltext.split("\n")) + "\n" 1069 1070 return hidltext 1071 1072 return hidldoc_formatter 1073 1074def dedent(text): 1075 """ 1076 Remove all common indentation from every line but the 0th. 1077 This will avoid getting <code> blocks when rendering text via markdown. 1078 Ignoring the 0th line will also allow the 0th line not to be aligned. 1079 1080 Args: 1081 text: A string of text to dedent. 1082 1083 Returns: 1084 String dedented by above rules. 1085 1086 For example: 1087 assertEquals("bar\nline1\nline2", dedent("bar\n line1\n line2")) 1088 assertEquals("bar\nline1\nline2", dedent(" bar\n line1\n line2")) 1089 assertEquals("bar\n line1\nline2", dedent(" bar\n line1\n line2")) 1090 """ 1091 text = textwrap.dedent(text) 1092 text_lines = text.split('\n') 1093 text_not_first = "\n".join(text_lines[1:]) 1094 text_not_first = textwrap.dedent(text_not_first) 1095 text = text_lines[0] + "\n" + text_not_first 1096 1097 return text 1098 1099def md(text, img_src_prefix="", table_ext=True): 1100 """ 1101 Run text through markdown to produce HTML. 1102 1103 This also removes all common indentation from every line but the 0th. 1104 This will avoid getting <code> blocks in markdown. 1105 Ignoring the 0th line will also allow the 0th line not to be aligned. 1106 1107 Args: 1108 text: A markdown-syntax using block of text to format. 1109 img_src_prefix: An optional string to prepend to each <img src="target"/> 1110 1111 Returns: 1112 String rendered by markdown and other rules applied (see above). 1113 1114 For example, this avoids the following situation: 1115 1116 <!-- Input --> 1117 1118 <!--- can't use dedent directly since 'foo' has no indent --> 1119 <notes>foo 1120 bar 1121 bar 1122 </notes> 1123 1124 <!-- Bad Output -- > 1125 <!-- if no dedent is done generated code looks like --> 1126 <p>foo 1127 <code><pre> 1128 bar 1129 bar</pre></code> 1130 </p> 1131 1132 Instead we get the more natural expected result: 1133 1134 <!-- Good Output --> 1135 <p>foo 1136 bar 1137 bar</p> 1138 1139 """ 1140 text = dedent(text) 1141 1142 # full list of extensions at http://pythonhosted.org/Markdown/extensions/ 1143 md_extensions = ['tables'] if table_ext else []# make <table> with ASCII |_| tables 1144 # render with markdown 1145 text = markdown.markdown(text, extensions=md_extensions) 1146 1147 # prepend a prefix to each <img src="foo"> -> <img src="${prefix}foo"> 1148 text = re.sub(r'src="([^"]*)"', 'src="' + img_src_prefix + r'\1"', text) 1149 return text 1150 1151def filter_tags(text, metadata, filter_function, summary_function = None): 1152 """ 1153 Find all references to tags in the form outer_namespace.xxx.yyy[.zzz] in 1154 the provided text, and pass them through filter_function and summary_function. 1155 1156 Used to linkify entry names in HMTL, javadoc output. 1157 1158 Args: 1159 text: A string representing a block of text destined for output 1160 metadata: A Metadata instance, the root of the metadata properties tree 1161 filter_function: A Node->string function to apply to each node 1162 when found in text; the string returned replaces the tag name in text. 1163 summary_function: A Node list->string function that is provided the list of 1164 unique tag nodes found in text, and which must return a string that is 1165 then appended to the end of the text. The list is sorted alphabetically 1166 by node name. 1167 """ 1168 1169 tag_set = set() 1170 def name_match(name): 1171 return lambda node: node.name == name 1172 1173 # Match outer_namespace.x.y or outer_namespace.x.y.z, making sure 1174 # to grab .z and not just outer_namespace.x.y. (sloppy, but since we 1175 # check for validity, a few false positives don't hurt). 1176 # Try to ignore items of the form {@link <outer_namespace>... 1177 for outer_namespace in metadata.outer_namespaces: 1178 1179 tag_match = r"(?<!\{@link\s)" + outer_namespace.name + \ 1180 r"\.([a-zA-Z0-9\n]+)\.([a-zA-Z0-9\n]+)(\.[a-zA-Z0-9\n]+)?([/]?)" 1181 1182 def filter_sub(match): 1183 whole_match = match.group(0) 1184 section1 = match.group(1) 1185 section2 = match.group(2) 1186 section3 = match.group(3) 1187 end_slash = match.group(4) 1188 1189 # Don't linkify things ending in slash (urls, for example) 1190 if end_slash: 1191 return whole_match 1192 1193 candidate = "" 1194 1195 # First try a two-level match 1196 candidate2 = "%s.%s.%s" % (outer_namespace.name, section1, section2) 1197 got_two_level = False 1198 1199 node = metadata.find_first(name_match(candidate2.replace('\n',''))) 1200 if not node and '\n' in section2: 1201 # Linefeeds are ambiguous - was the intent to add a space, 1202 # or continue a lengthy name? Try the former now. 1203 candidate2b = "%s.%s.%s" % (outer_namespace.name, section1, section2[:section2.find('\n')]) 1204 node = metadata.find_first(name_match(candidate2b)) 1205 if node: 1206 candidate2 = candidate2b 1207 1208 if node: 1209 # Have two-level match 1210 got_two_level = True 1211 candidate = candidate2 1212 elif section3: 1213 # Try three-level match 1214 candidate3 = "%s%s" % (candidate2, section3) 1215 node = metadata.find_first(name_match(candidate3.replace('\n',''))) 1216 1217 if not node and '\n' in section3: 1218 # Linefeeds are ambiguous - was the intent to add a space, 1219 # or continue a lengthy name? Try the former now. 1220 candidate3b = "%s%s" % (candidate2, section3[:section3.find('\n')]) 1221 node = metadata.find_first(name_match(candidate3b)) 1222 if node: 1223 candidate3 = candidate3b 1224 1225 if node: 1226 # Have 3-level match 1227 candidate = candidate3 1228 1229 # Replace match with crossref or complain if a likely match couldn't be matched 1230 1231 if node: 1232 tag_set.add(node) 1233 return whole_match.replace(candidate,filter_function(node)) 1234 else: 1235 print(" WARNING: Could not crossref likely reference {%s}" % (match.group(0)), 1236 file=sys.stderr) 1237 return whole_match 1238 1239 text = re.sub(tag_match, filter_sub, text) 1240 1241 if summary_function is not None: 1242 return text + summary_function(sorted(tag_set, key=lambda x: x.name)) 1243 else: 1244 return text 1245 1246def ndk_replace_tag_wildcards(text, metadata): 1247 """ 1248 Find all references to tags in the form android.xxx.* or android.xxx.yyy.* 1249 in the provided text, and replace them by NDK format of "ACAMERA_XXX_*" or 1250 "ACAMERA_XXX_YYY_*" 1251 1252 Args: 1253 text: A string representing a block of text destined for output 1254 metadata: A Metadata instance, the root of the metadata properties tree 1255 """ 1256 tag_match = r"android\.([a-zA-Z0-9\n]+)\.\*" 1257 tag_match_2 = r"android\.([a-zA-Z0-9\n]+)\.([a-zA-Z0-9\n]+)\*" 1258 1259 def filter_sub(match): 1260 return "ACAMERA_" + match.group(1).upper() + "_*" 1261 def filter_sub_2(match): 1262 return "ACAMERA_" + match.group(1).upper() + match.group(2).upper() + "_*" 1263 1264 text = re.sub(tag_match, filter_sub, text) 1265 text = re.sub(tag_match_2, filter_sub_2, text) 1266 return text 1267 1268def filter_links(text, filter_function, summary_function = None): 1269 """ 1270 Find all references to tags in the form {@link xxx#yyy [zzz]} in the 1271 provided text, and pass them through filter_function and 1272 summary_function. 1273 1274 Used to linkify documentation cross-references in HMTL, javadoc output. 1275 1276 Args: 1277 text: A string representing a block of text destined for output 1278 metadata: A Metadata instance, the root of the metadata properties tree 1279 filter_function: A (string, string)->string function to apply to each 'xxx#yyy', 1280 zzz pair when found in text; the string returned replaces the tag name in text. 1281 summary_function: A string list->string function that is provided the list of 1282 unique targets found in text, and which must return a string that is 1283 then appended to the end of the text. The list is sorted alphabetically 1284 by node name. 1285 1286 """ 1287 1288 target_set = set() 1289 def name_match(name): 1290 return lambda node: node.name == name 1291 1292 tag_match = r"\{@link\s+([^\s\}\|]+)(?:\|([^\s\}]+))*([^\}]*)\}" 1293 1294 def filter_sub(match): 1295 whole_match = match.group(0) 1296 target = match.group(1) 1297 target_ndk = match.group(2) 1298 shortname = match.group(3).strip() 1299 1300 #print("Found link '%s' ndk '%s' as '%s' -> '%s'" % (target, target_ndk, shortname, filter_function(target, target_ndk, shortname))) 1301 1302 # Replace match with crossref 1303 target_set.add(target) 1304 return filter_function(target, target_ndk, shortname) 1305 1306 text = re.sub(tag_match, filter_sub, text) 1307 1308 if summary_function is not None: 1309 return text + summary_function(sorted(target_set)) 1310 else: 1311 return text 1312 1313def any_visible(section, kind_name, visibilities): 1314 """ 1315 Determine if entries in this section have an applied visibility that's in 1316 the list of given visibilities. 1317 1318 Args: 1319 section: A section of metadata 1320 kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls' 1321 visibilities: An iterable of visibilities to match against 1322 1323 Returns: 1324 True if the section has any entries with any of the given visibilities. False otherwise. 1325 """ 1326 1327 for inner_namespace in get_children_by_filtering_kind(section, kind_name, 1328 'namespaces'): 1329 if any(filter_visibility(inner_namespace.merged_entries, visibilities)): 1330 return True 1331 1332 return any(filter_visibility(get_children_by_filtering_kind(section, kind_name, 1333 'merged_entries'), 1334 visibilities)) 1335 1336 1337def filter_visibility(entries, visibilities): 1338 """ 1339 Remove entries whose applied visibility is not in the supplied visibilities. 1340 1341 Args: 1342 entries: An iterable of Entry nodes 1343 visibilities: An iterable of visibilities to filter against 1344 1345 Yields: 1346 An iterable of Entry nodes 1347 """ 1348 return (e for e in entries if e.applied_visibility in visibilities) 1349 1350def remove_synthetic_or_fwk_only(entries): 1351 """ 1352 Filter the given entries by removing those that are synthetic or fwk_only. 1353 1354 Args: 1355 entries: An iterable of Entry nodes 1356 1357 Yields: 1358 An iterable of Entry nodes 1359 """ 1360 return (e for e in entries if not (e.synthetic or e.visibility == 'fwk_only')) 1361 1362def remove_synthetic(entries): 1363 """ 1364 Filter the given entries by removing those that are synthetic. 1365 1366 Args: 1367 entries: An iterable of Entry nodes 1368 1369 Yields: 1370 An iterable of Entry nodes 1371 """ 1372 return (e for e in entries if not e.synthetic) 1373 1374def filter_added_in_hal_version(entries, hal_major_version, hal_minor_version): 1375 """ 1376 Filter the given entries to those added in the given HIDL HAL version 1377 1378 Args: 1379 entries: An iterable of Entry nodes 1380 hal_major_version: Major HIDL version to filter for 1381 hal_minor_version: Minor HIDL version to filter for 1382 1383 Yields: 1384 An iterable of Entry nodes 1385 """ 1386 return (e for e in entries if e.hal_major_version == hal_major_version and e.hal_minor_version == hal_minor_version) 1387 1388def filter_has_enum_values_added_in_hal_version(entries, hal_major_version, hal_minor_version): 1389 """ 1390 Filter the given entries to those that have a new enum value added in the given HIDL HAL version 1391 1392 Args: 1393 entries: An iterable of Entry nodes 1394 hal_major_version: Major HIDL version to filter for 1395 hal_minor_version: Minor HIDL version to filter for 1396 1397 Yields: 1398 An iterable of Entry nodes 1399 """ 1400 return (e for e in entries if e.has_new_values_added_in_hal_version(hal_major_version, hal_minor_version)) 1401 1402def permission_needed_count(root): 1403 """ 1404 Return the number entries that need camera permission. 1405 1406 Args: 1407 root: a Metadata instance 1408 1409 Returns: 1410 The number of entires that need camera permission. 1411 1412 """ 1413 ret = 0 1414 for sec in find_all_sections(root): 1415 ret += len(list(filter_has_permission_needed(remove_synthetic_or_fwk_only(find_unique_entries(sec))))) 1416 1417 return ret 1418 1419def filter_has_permission_needed(entries): 1420 """ 1421 Filter the given entries by removing those that don't need camera permission. 1422 1423 Args: 1424 entries: An iterable of Entry nodes 1425 1426 Yields: 1427 An iterable of Entry nodes 1428 """ 1429 return (e for e in entries if e.permission_needed == 'true') 1430 1431def filter_ndk_visible(entries): 1432 """ 1433 Filter the given entries by removing those that are not NDK visible. 1434 1435 Args: 1436 entries: An iterable of Entry nodes 1437 1438 Yields: 1439 An iterable of Entry nodes 1440 """ 1441 return (e for e in entries if e.applied_ndk_visible == 'true') 1442 1443def wbr(text): 1444 """ 1445 Insert word break hints for the browser in the form of <wbr> HTML tags. 1446 1447 Word breaks are inserted inside an HTML node only, so the nodes themselves 1448 will not be changed. Attributes are also left unchanged. 1449 1450 The following rules apply to insert word breaks: 1451 - For characters in [ '.', '/', '_' ] 1452 - For uppercase letters inside a multi-word X.Y.Z (at least 3 parts) 1453 1454 Args: 1455 text: A string of text containing HTML content. 1456 1457 Returns: 1458 A string with <wbr> inserted by the above rules. 1459 """ 1460 SPLIT_CHARS_LIST = ['.', '_', '/'] 1461 SPLIT_CHARS = r'([.|/|_/,]+)' # split by these characters 1462 CAP_LETTER_MIN = 3 # at least 3 components split by above chars, i.e. x.y.z 1463 def wbr_filter(text): 1464 new_txt = text 1465 1466 # for johnyOrange.appleCider.redGuardian also insert wbr before the caps 1467 # => johny<wbr>Orange.apple<wbr>Cider.red<wbr>Guardian 1468 for words in text.split(" "): 1469 for char in SPLIT_CHARS_LIST: 1470 # match at least x.y.z, don't match x or x.y 1471 if len(words.split(char)) >= CAP_LETTER_MIN: 1472 new_word = re.sub(r"([a-z])([A-Z])", r"\1<wbr>\2", words) 1473 new_txt = new_txt.replace(words, new_word) 1474 1475 # e.g. X/Y/Z -> X/<wbr>Y/<wbr>/Z. also for X.Y.Z, X_Y_Z. 1476 new_txt = re.sub(SPLIT_CHARS, r"\1<wbr>", new_txt) 1477 1478 return new_txt 1479 1480 # Do not mangle HTML when doing the replace by using BeatifulSoup 1481 # - Use the 'html.parser' to avoid inserting <html><body> when decoding 1482 soup = bs4.BeautifulSoup(text, features='html.parser') 1483 wbr_tag = lambda: soup.new_tag('wbr') # must generate new tag every time 1484 1485 for navigable_string in soup.findAll(text=True): 1486 parent = navigable_string.parent 1487 1488 # Insert each '$text<wbr>$foo' before the old '$text$foo' 1489 split_by_wbr_list = wbr_filter(navigable_string).split("<wbr>") 1490 for (split_string, last) in enumerate_with_last(split_by_wbr_list): 1491 navigable_string.insert_before(split_string) 1492 1493 if not last: 1494 # Note that 'insert' will move existing tags to this spot 1495 # so make a new tag instead 1496 navigable_string.insert_before(wbr_tag()) 1497 1498 # Remove the old unmodified text 1499 navigable_string.extract() 1500 1501 return soup.decode() 1502 1503def copyright_year(): 1504 return _copyright_year 1505 1506def hal_major_version(): 1507 return _hal_major_version 1508 1509def hal_minor_version(): 1510 return _hal_minor_version 1511 1512def first_hal_minor_version(hal_major_version): 1513 return 2 if hal_major_version == 3 else 0 1514 1515def find_all_sections_added_in_hal(root, hal_major_version, hal_minor_version): 1516 """ 1517 Find all descendants that are Section or InnerNamespace instances, which 1518 were added in HIDL HAL version major.minor. The section is defined to be 1519 added in a HAL version iff the lowest HAL version number of its entries is 1520 that HAL version. 1521 1522 Args: 1523 root: a Metadata instance 1524 hal_major/minor_version: HAL version numbers 1525 1526 Returns: 1527 A list of Section/InnerNamespace instances 1528 1529 Remarks: 1530 These are known as "sections" in the generated C code. 1531 """ 1532 all_sections = find_all_sections(root) 1533 new_sections = [] 1534 for section in all_sections: 1535 min_major_version = None 1536 min_minor_version = None 1537 for entry in remove_synthetic_or_fwk_only(find_unique_entries(section)): 1538 min_major_version = (min_major_version or entry.hal_major_version) 1539 min_minor_version = (min_minor_version or entry.hal_minor_version) 1540 if entry.hal_major_version < min_major_version or \ 1541 (entry.hal_major_version == min_major_version and entry.hal_minor_version < min_minor_version): 1542 min_minor_version = entry.hal_minor_version 1543 min_major_version = entry.hal_major_version 1544 if min_major_version == hal_major_version and min_minor_version == hal_minor_version: 1545 new_sections.append(section) 1546 return new_sections 1547 1548def find_first_older_used_hal_version(section, hal_major_version, hal_minor_version): 1549 hal_version = (0, 0) 1550 for v in section.hal_versions: 1551 if (v[0] > hal_version[0] or (v[0] == hal_version[0] and v[1] > hal_version[1])) and \ 1552 (v[0] < hal_major_version or (v[0] == hal_major_version and v[1] < hal_minor_version)): 1553 hal_version = v 1554 return hal_version 1555