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