entity.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. #
  2. # Copyright 2021 Espressif Systems (Shanghai) CO LTD
  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. import collections
  17. import fnmatch
  18. import os
  19. from enum import Enum
  20. from functools import total_ordering
  21. from pyparsing import (Group, Literal, OneOrMore, ParseException, SkipTo, Suppress, White, Word, ZeroOrMore, alphas,
  22. nums, restOfLine)
  23. @total_ordering
  24. class Entity():
  25. """
  26. An entity refers to a library, object, symbol whose input
  27. sections can be placed or excluded from placement.
  28. An important property of an entity is its specificity - the granularity
  29. of the the entity to be placed. Specificity increases in the following
  30. order: library, object, symbol. An entity with no specificity refers
  31. to all entities.
  32. """
  33. ALL = '*'
  34. class Specificity(Enum):
  35. NONE = 0
  36. ARCHIVE = 1
  37. OBJ = 2
  38. SYMBOL = 3
  39. def __init__(self, archive=None, obj=None, symbol=None):
  40. archive_spec = archive and archive != Entity.ALL
  41. obj_spec = obj and obj != Entity.ALL
  42. symbol_spec = symbol and symbol != Entity.ALL
  43. if not archive_spec and not obj_spec and not symbol_spec:
  44. self.specificity = Entity.Specificity.NONE
  45. elif archive_spec and not obj_spec and not symbol_spec:
  46. self.specificity = Entity.Specificity.ARCHIVE
  47. elif archive_spec and obj_spec and not symbol_spec:
  48. self.specificity = Entity.Specificity.OBJ
  49. elif archive_spec and obj_spec and symbol_spec:
  50. self.specificity = Entity.Specificity.SYMBOL
  51. else:
  52. raise ValueError("Invalid arguments '(%s, %s, %s)'" % (archive, obj, symbol))
  53. self.archive = archive
  54. self.obj = obj
  55. self.symbol = symbol
  56. def __eq__(self, other):
  57. return (self.specificity.value == other.specificity.value and
  58. self.archive == other.archive and
  59. self.obj == other.obj and
  60. self.symbol == other.symbol)
  61. def __lt__(self, other):
  62. res = False
  63. if self.specificity.value < other.specificity.value:
  64. res = True
  65. elif self.specificity == other.specificity:
  66. for s in Entity.Specificity:
  67. a = self[s] if self[s] else ''
  68. b = other[s] if other[s] else ''
  69. if a != b:
  70. res = a < b
  71. break
  72. else:
  73. res = False
  74. return res
  75. def __hash__(self):
  76. return hash(self.__repr__())
  77. def __str__(self):
  78. return '%s:%s %s' % self.__repr__()
  79. def __repr__(self):
  80. return (self.archive, self.obj, self.symbol)
  81. def __getitem__(self, spec):
  82. res = None
  83. if spec == Entity.Specificity.ARCHIVE:
  84. res = self.archive
  85. elif spec == Entity.Specificity.OBJ:
  86. res = self.obj
  87. elif spec == Entity.Specificity.SYMBOL:
  88. res = self.symbol
  89. else:
  90. res = None
  91. return res
  92. class EntityDB():
  93. """
  94. Collection of entities extracted from libraries known in the build.
  95. Allows retrieving a list of archives, a list of object files in an archive
  96. or a list of symbols in an archive; as well as allows for checking if an
  97. entity exists in the collection.
  98. """
  99. __info = collections.namedtuple('__info', 'filename content')
  100. def __init__(self):
  101. self.sections = dict()
  102. def add_sections_info(self, sections_info_dump):
  103. first_line = sections_info_dump.readline()
  104. archive_path = (Literal('In archive').suppress() +
  105. White().suppress() +
  106. # trim the colon and line ending characters from archive_path
  107. restOfLine.setResultsName('archive_path').setParseAction(lambda s, loc, toks: s.rstrip(':\n\r ')))
  108. parser = archive_path
  109. results = None
  110. try:
  111. results = parser.parseString(first_line, parseAll=True)
  112. except ParseException as p:
  113. raise ParseException('Parsing sections info for library ' + sections_info_dump.name + ' failed. ' + p.msg)
  114. archive = os.path.basename(results.archive_path)
  115. self.sections[archive] = EntityDB.__info(sections_info_dump.name, sections_info_dump.read())
  116. def _get_infos_from_file(self, info):
  117. # {object}: file format elf32-xtensa-le
  118. object_line = SkipTo(':').setResultsName('object') + Suppress(restOfLine)
  119. # Sections:
  120. # Idx Name ...
  121. section_start = Suppress(Literal('Sections:'))
  122. section_header = Suppress(OneOrMore(Word(alphas)))
  123. # 00 {section} 0000000 ...
  124. # CONTENTS, ALLOC, ....
  125. section_entry = Suppress(Word(nums)) + SkipTo(' ') + Suppress(restOfLine) + \
  126. Suppress(ZeroOrMore(Word(alphas) + Literal(',')) + Word(alphas))
  127. content = Group(object_line + section_start + section_header + Group(OneOrMore(section_entry)).setResultsName('sections'))
  128. parser = Group(ZeroOrMore(content)).setResultsName('contents')
  129. results = None
  130. try:
  131. results = parser.parseString(info.content, parseAll=True)
  132. except ParseException as p:
  133. raise ParseException('Unable to parse section info file ' + info.filename + '. ' + p.msg)
  134. return results
  135. def _process_archive(self, archive):
  136. stored = self.sections[archive]
  137. # Parse the contents of the sections file on-demand,
  138. # save the result for later
  139. if not isinstance(stored, dict):
  140. parsed = self._get_infos_from_file(stored)
  141. stored = dict()
  142. for content in parsed.contents:
  143. sections = list(map(lambda s: s, content.sections))
  144. stored[content.object] = sections
  145. self.sections[archive] = stored
  146. def get_archives(self):
  147. return self.sections.keys()
  148. def get_objects(self, archive):
  149. try:
  150. self._process_archive(archive)
  151. except KeyError:
  152. return []
  153. return self.sections[archive].keys()
  154. def _match_obj(self, archive, obj):
  155. objs = self.get_objects(archive)
  156. match_objs = fnmatch.filter(objs, obj + '.o') + fnmatch.filter(objs, obj + '.*.obj') + fnmatch.filter(objs, obj + '.obj')
  157. if len(match_objs) > 1:
  158. raise ValueError("Multiple matches for object: '%s: %s': %s" % (archive, obj, str(match_objs)))
  159. try:
  160. return match_objs[0]
  161. except IndexError:
  162. return None
  163. def get_sections(self, archive, obj):
  164. obj = self._match_obj(archive, obj)
  165. res = []
  166. if obj:
  167. res = self.sections[archive][obj]
  168. return res
  169. def _match_symbol(self, archive, obj, symbol):
  170. sections = self.get_sections(archive, obj)
  171. return [s for s in sections if s.endswith(symbol)]
  172. def check_exists(self, entity):
  173. res = True
  174. if entity.specificity != Entity.Specificity.NONE:
  175. if entity.specificity == Entity.Specificity.ARCHIVE:
  176. res = entity.archive in self.get_archives()
  177. elif entity.specificity == Entity.Specificity.OBJ:
  178. res = self._match_obj(entity.archive, entity.obj) is not None
  179. elif entity.specificity == Entity.Specificity.SYMBOL:
  180. res = len(self._match_symbol(entity.archive, entity.obj, entity.symbol)) > 0
  181. else:
  182. res = False
  183. return res