Files
esp-idf/tools/ldgen/ldgen/entity.py

232 lines
7.8 KiB
Python

#
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
#
import collections
import fnmatch
import os
from enum import Enum
from functools import total_ordering
from pyparsing import alphas
from pyparsing import Group
from pyparsing import Literal
from pyparsing import nums
from pyparsing import OneOrMore
from pyparsing import ParseException
from pyparsing import Regex
from pyparsing import rest_of_line
from pyparsing import SkipTo
from pyparsing import Suppress
from pyparsing import White
from pyparsing import Word
from pyparsing import ZeroOrMore
@total_ordering
class Entity:
"""
An entity refers to a library, object, symbol whose input
sections can be placed or excluded from placement.
An important property of an entity is its specificity - the granularity
of the the entity to be placed. Specificity increases in the following
order: library, object, symbol. An entity with no specificity refers
to all entities.
"""
ALL = '*'
class Specificity(Enum):
NONE = 0
ARCHIVE = 1
OBJ = 2
SYMBOL = 3
def __init__(self, archive=None, obj=None, symbol=None):
archive_spec = archive and archive != Entity.ALL
obj_spec = obj and obj != Entity.ALL
symbol_spec = symbol and symbol != Entity.ALL
if not archive_spec and not obj_spec and not symbol_spec:
self.specificity = Entity.Specificity.NONE
elif archive_spec and not obj_spec and not symbol_spec:
self.specificity = Entity.Specificity.ARCHIVE
elif archive_spec and obj_spec and not symbol_spec:
self.specificity = Entity.Specificity.OBJ
elif archive_spec and obj_spec and symbol_spec:
self.specificity = Entity.Specificity.SYMBOL
else:
raise ValueError("Invalid arguments '(%s, %s, %s)'" % (archive, obj, symbol))
self.archive = archive
self.obj = obj
self.symbol = symbol
def __eq__(self, other):
return (self.specificity.value == other.specificity.value and
self.archive == other.archive and
self.obj == other.obj and
self.symbol == other.symbol)
def __lt__(self, other):
res = False
if self.specificity.value < other.specificity.value:
res = True
elif self.specificity == other.specificity:
for s in Entity.Specificity:
a = self[s] if self[s] else ''
b = other[s] if other[s] else ''
if a != b:
res = a < b
break
else:
res = False
return res
def __hash__(self):
return hash(self.__repr__())
def __str__(self):
return '%s:%s %s' % self.__repr__()
def __repr__(self):
return self.archive, self.obj, self.symbol
def __getitem__(self, spec):
res = None
if spec == Entity.Specificity.ARCHIVE:
res = self.archive
elif spec == Entity.Specificity.OBJ:
res = self.obj
elif spec == Entity.Specificity.SYMBOL:
res = self.symbol
else:
res = None
return res
class EntityDB:
"""
Collection of entities extracted from libraries known in the build.
Allows retrieving a list of archives, a list of object files in an archive
or a list of symbols in an archive; as well as allows for checking if an
entity exists in the collection.
"""
__info = collections.namedtuple('__info', 'filename content')
def __init__(self):
self.sections = dict()
def add_sections_info(self, sections_info_dump):
first_line = sections_info_dump.readline()
archive_path = (Literal('In archive').suppress() +
White().suppress() +
# trim the colon and line ending characters from archive_path
rest_of_line.set_results_name('archive_path').set_parse_action(
lambda s, loc, toks: s.rstrip(':\n\r ')))
parser = archive_path
try:
results = parser.parseString(first_line, parseAll=True)
except ParseException as p:
raise ParseException('Parsing sections info for library ' + sections_info_dump.name + ' failed. ' + p.msg)
archive = os.path.basename(results.archive_path)
self.sections[archive] = EntityDB.__info(sections_info_dump.name, sections_info_dump.read())
def _get_infos_from_file(self, info):
# {object}: file format elf32-xtensa-le
object_line = SkipTo(':').set_results_name('object') + Suppress(rest_of_line)
# Sections:
# Idx Name ...
section_start = Suppress(Literal('Sections:'))
section_header = Suppress(OneOrMore(Word(alphas)))
# 00 {section} 0000000 ...
# CONTENTS, ALLOC, ....
section_entry = (Suppress(Word(nums)) + Regex(r'\.\S+') + Suppress(rest_of_line)
+ Suppress(ZeroOrMore(Word(alphas + '_') + Literal(',')) + Word(alphas + '_')))
content = Group(object_line
+ section_start
+ section_header
+ Group(OneOrMore(section_entry)).set_results_name('sections'))
parser = Group(ZeroOrMore(content)).set_results_name('contents')
try:
results = parser.parseString(info.content, parseAll=True)
except ParseException as p:
raise ParseException('Unable to parse section info file ' + info.filename + '. ' + p.msg)
return results
def _process_archive(self, archive):
stored = self.sections[archive]
# Parse the contents of the sections file on-demand,
# save the result for later
if not isinstance(stored, dict):
parsed = self._get_infos_from_file(stored)
stored = dict()
for content in parsed.contents:
sections = list(map(lambda s: s, content.sections))
stored[content.object] = sections
self.sections[archive] = stored
def get_archives(self):
return self.sections.keys()
def get_objects(self, archive):
try:
self._process_archive(archive)
except KeyError:
return []
return self.sections[archive].keys()
def _match_obj(self, archive, obj):
objs = self.get_objects(archive)
match_objs = (fnmatch.filter(objs, obj + '.*.o')
+ fnmatch.filter(objs, obj + '.o')
+ fnmatch.filter(objs, obj + '.*.obj')
+ fnmatch.filter(objs, obj + '.obj'))
if len(match_objs) > 1:
raise ValueError("Multiple matches for object: '%s: %s': %s" % (archive, obj, str(match_objs)))
try:
return match_objs[0]
except IndexError:
return None
def get_sections(self, archive, obj):
obj = self._match_obj(archive, obj)
res = []
if obj:
res = self.sections[archive][obj]
return res
def _match_symbol(self, archive, obj, symbol):
sections = self.get_sections(archive, obj)
return [s for s in sections if s.endswith(symbol)]
def check_exists(self, entity):
res = True
if entity.specificity != Entity.Specificity.NONE:
if entity.specificity == Entity.Specificity.ARCHIVE:
res = entity.archive in self.get_archives()
elif entity.specificity == Entity.Specificity.OBJ:
res = self._match_obj(entity.archive, entity.obj) is not None
elif entity.specificity == Entity.Specificity.SYMBOL:
res = len(self._match_symbol(entity.archive, entity.obj, entity.symbol)) > 0
else:
res = False
return res