#! /usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
#
# Copyright (C) 2018, ARM Limited and contributors.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numbers
from exekall.engine import ValueDB
from exekall.utils import out, get_name, NoValue, get_subclasses
[docs]
class AdaptorBase:
"""
Base class of all adaptors.
:param args: Command line argument namespace as returned by
:meth:`argparse.ArgumentParser.parse_args`. Depending on the subcommand
used, it could be the arguments of ``exekall run`` or ``exekall
compare`` (or any other subcommand).
:type args: argparse.Namespace
An adaptor is a class providing a number of hooks to customize the
behaviour of exekall on a given codebase. It should be implemented in a
module called ``exekall_customize`` in order to be found by ``exekall
run``.
"""
name = 'default'
def __init__(self, args):
self.args = args
[docs]
def get_non_reusable_type_set(self):
"""
Return a set of non-reusable types.
Defaults to an empty set.
"""
return set()
[docs]
def filter_op_set(self, op_set):
"""
Returns a potentially filtered set of :class:`exekall.engine.Operator`.
This allows removing some operators from the ones that will be used to
build expressions. Defaults to filtering out operators without any
parameter, since the "spark" values are usually introduced by the
adaptor directly, instead of calling functions from the code base.
"""
return {
op for op in op_set
# Only select operators with non-empty parameter list. This
# rules out all classes __init__ that do not take parameter, as
# they are typically not interesting to us.
if op.prototype[0]
}
[docs]
def get_prebuilt_op_set(self):
"""
Returns a set of :class:`exekall.engine.PrebuiltOperator`.
This allows injecting any "spark" value that is needed to build the
expressions, like configuration objects. These values are usually built
out of the custom CLI parameters added by the adaptor.
"""
return set()
[docs]
def get_hidden_op_set(self, op_set):
"""
Returns the set of hidden :class:`exekall.engine.Operator`.
This allows hiding parts of the IDs that would not add much information
but clutter them.
"""
self.hidden_op_set = set()
return self.hidden_op_set
[docs]
@staticmethod
def register_run_param(parser):
"""
Register CLI parameters for the ``run`` subcommand.
:param parser: Parser of the ``run`` subcommand to add arguments onto.
:type parser: argparse.ArgumentParser
"""
pass
[docs]
@staticmethod
def register_compare_param(parser):
"""
Register CLI parameters for the ``compare`` subcommand.
:param parser: Parser of the ``compare`` subcommand to add arguments onto.
:type parser: argparse.ArgumentParser
"""
pass
[docs]
def compare_db_list(self, db_list):
"""
Compare databases listed in ``db_list``.
:param db_list: List of :class:`exekall.engine.ValueDB` to compare.
:type db_list: list(exekall.engine.ValueDB)
This is called by ``exekall compare`` to actually do something useful.
"""
pass
[docs]
@staticmethod
def register_show_param(parser):
"""
Register CLI parameters for the ``show`` subcommand.
:param parser: Parser of the ``show`` subcommand to add arguments onto.
:type parser: argparse.ArgumentParser
"""
pass
[docs]
def show_db(self, db):
"""
Show the content of the database.
:param db: :class:`exekall.engine.ValueDB` to show.
:type db: exekall.engine.ValueDB
This is called by ``exekall show`` to actually do something useful.
"""
root_expr_val_list = sorted(db.get_roots(), key=lambda expr_val: expr_val.uuid)
for i, froz_val in enumerate(root_expr_val_list):
print('{}) {}\n'.format(i, froz_val.format_structure(full_qual=False)))
return 0
[docs]
@staticmethod
def get_default_type_goal_pattern_set():
"""
Returns a set of patterns that will be used as the default value for
``exekall run --goal``.
"""
return {'*Result'}
[docs]
@classmethod
def reload_db(cls, db, path=None):
"""
Hook called when reloading a serialized
:class:`exekall.engine.ValueDB`. The returned database will be used.
:param db: :class:`exekall.engine.ValueDB` that has just been
deserialized.
:type db: exekall.engine.ValueDB
:param path: Path of the file of the serialized database if available.
:type path: str or None
"""
return db
[docs]
def finalize_expr(self, expr):
"""
Finalize an :class:`exekall.engine.ComputableExpression` right after
all its values have been computed.
"""
pass
[docs]
def get_summary(self, result_map):
"""
Return the summary of an ``exekall run`` session as a string.
:param result_map: Dictionary of expressions to the list of their
values.
:type result_map: dict(exekall.engine.ComputableExpression,
list(exekall.engine.ExprVal))
"""
hidden_callable_set = {
op.callable_
for op in self.hidden_op_set
}
# Get all IDs and compute the maximum length to align the output
result_id_map = {
result: result.get_id(
hidden_callable_set=hidden_callable_set,
full_qual=False,
qual=False,
)
for expr, result_list in result_map.items()
for result in result_list
}
max_id_len = len(max(result_id_map.values(), key=len))
summary = []
for expr, result_list in result_map.items():
for result in result_list:
msg = self.format_result(result)
msg = msg + '\n' if '\n' in msg else msg
summary.append('{id:<{max_id_len}} UUID={uuid} {result}'.format(
id=result_id_map[result],
uuid=result.uuid,
result=msg,
max_id_len=max_id_len,
))
return '\n'.join(summary)
[docs]
def get_run_exit_code(self, result_map):
"""
Return an integer used as ``exekall run`` exit status.
:param result_map: Dictionary of expressions to the list of their
values.
:type result_map: dict(exekall.engine.ComputableExpression,
exekall.engine.ExprVal)
"""
return 0
[docs]
@classmethod
def get_adaptor_cls(cls, name=None):
"""
Return the adaptor class that has the name ``name``.
.. note:: This is not intended to be overriden by subclasses.
"""
subcls_list = list(get_subclasses(cls) - {cls})
if not name:
if len(subcls_list) > 1:
raise ValueError('An adaptor name must be specified if there is more than one adaptor to choose from')
else:
if len(subcls_list) > 0:
return subcls_list[0]
else:
return cls
for subcls in subcls_list:
if subcls.name == name:
return subcls
return None