olindgre 2ece01e1d7 Make powertrain-build not overlap with pybuild in site-packages
Change-Id: I7b59f3f04f0f787d35db0b9389f295bf1ad24f56
2024-09-17 10:25:04 +02:00

297 lines
11 KiB
Python

# Copyright 2024 Volvo Car Corporation
# Licensed under Apache 2.0.
"""Module for handling the Service abstraction."""
import re
from powertrain_build.interface.base import filter_signals
from powertrain_build.interface.csp_api import CspApi
from powertrain_build.interface.application import get_internal_domain
from powertrain_build.lib import logger
LOGGER = logger.create_logger("service")
def get_service(app, client_name, interface):
"""Get service implementation specification"""
rasters = app.get_rasters()
LOGGER.debug("Rasters: %s", rasters)
translation_files = app.get_translation_files()
sfw = ServiceFramework(app)
sfw.filter = f"{client_name}_internal"
sfw.name = f"{client_name}_{interface}"
sfw.parse_definition(translation_files)
internal = get_internal_domain(rasters)
properties_from_json = [
{"destination": "min", "source": "min", "default": "-"},
{"destination": "max", "source": "max", "default": "-"},
{"destination": "variable_type", "source": "type"},
{"destination": "offset", "source": "offset", "default": "-"},
{"destination": "factor", "source": "lsb", "default": 1},
{"destination": "description", "source": "description"},
{"destination": "unit", "source": "unit", "default": "-"},
]
for raster in rasters:
external_signals = filter_signals(raster.insignals, internal)
sfw.add_signals(
external_signals,
"insignals",
properties_from_json,
)
sfw.add_signals(raster.outsignals, "outsignals", properties_from_json)
return sfw.to_model(interface)
def get_service_list(app):
"""Get service list from app
Args:
app (Application): Pybuild project
Returns:
(str): a string containing the translated service list
"""
translation_map = app.get_service_mapping()
cmake = ''
for proxy, service in translation_map.items():
lib = re.sub('-', '_', service + '_lib' + proxy + '_service_proxy').upper()
include = re.sub('-', '_', service + '_include_dir').upper()
cmake += f"LIST(APPEND extra_libraries ${{{lib}}})\n"
cmake += f"LIST(APPEND EXTRA_INCLUDE_DIRS ${{{include}}})\n"
return cmake
class ServiceFramework(CspApi):
"""Service Framework abstraction layer"""
def __repr__(self):
"""String representation of SWFL"""
return (
f"<SWFL {self.name}"
f" app_side insignals: {len(self.signal_names['app']['insignals'])}"
f" app_side outsignals: {len(self.signal_names['app']['outsignals'])}>"
)
def get_map_file(self):
"""Get service translation map file
Returns:
(Path): service translation map file
"""
return self.base_application.get_services_file()
def get_map(self):
"""Get service translation map
Returns:
(dict): service translation map
"""
return self.base_application.get_service_mapping()
def check_endpoints(self):
"""Check and crash if signal endpoint contains both produces and consumes signals."""
endpoints = {}
for signal_name, signal_specs in self.translations.items():
if signal_name in self.signal_names["app"]['insignals']:
consumed = True
elif signal_name in self.signal_names["app"]['outsignals']:
consumed = False
else:
continue
for signal_spec in signal_specs:
endpoint = signal_spec[self.position.endpoint.value]
api = signal_spec[self.position.api.value]
key = (api, endpoint)
if key not in endpoints:
endpoints[key] = {
"consumed": consumed,
"signals": set()
}
endpoints[key]["signals"].add(signal_name)
assert consumed == endpoints[key]["consumed"], \
f"Signal endpoint {endpoint} for {api} contains both consumed and produced signals"
def extract_endpoint_definitions(self, raw):
"""Extract endpoint definitions from yaml file.
Args:
raw (dict): Raw yaml file
Returns:
(dict): Endpoint definitions
"""
self.parse_api_definitions(raw.get("service", {}))
@staticmethod
def extract_definition(definition):
"""Extract definition from yaml file.
Returns the properties and methods for a service.
Args:
definition (dict): Definition from yaml file
Returns:
(dict): Specifications for a service
"""
specifications = {}
specifications['properties'] = definition.get('properties', [])
specifications['methods'] = definition.get('methods', [])
return specifications
def to_model(self, client):
"""Method to generate dict to be saved as yaml
Args:
client (str): Name of the client in signal comm
Returns:
spec (dict): Signal model for using a service
"""
properties, types = self.properties_service_model(client)
descriptions = {
'internal': {
'brief': "Internal interface for associated application.",
'full': "This interface should only be used by the associated application."
},
'external': {
'brief': "External interface.",
'full': "This interface should be used by modules wanting to interact with the associated application."
},
'observer': {
'brief': "Read-only interface.",
'full': "This interface can be used by anyone wanting information from the associated application."
},
}
model = {"name": self.name,
"version": "${SERVICE_VERSION}",
"description": descriptions[client],
"properties": properties,
"types": types}
return model
def properties_service_model(self, client):
"""Generate properties and types for a service
Args:
client (str): Name of the client in signal comm
Returns:
(list): List of properties
(list): List of types
"""
properties = {}
types = {}
accessors = {}
if client == 'internal':
accessors['insignals'] = 'r-'
accessors['outsignals'] = '-w'
elif client == 'external':
accessors['insignals'] = '-w'
accessors['outsignals'] = 'r-'
else:
accessors['insignals'] = 'r-'
accessors['outsignals'] = 'r-'
properties_in, types_in = self._properties_service_model(
self.signal_names["app"]["insignals"],
accessors['insignals'])
properties_out, types_out = self._properties_service_model(
self.signal_names["app"]["outsignals"],
accessors['outsignals'])
properties = properties_in + properties_out
types = types_in + types_out
return properties, types
def _specifications(self, signal_names):
""" Iterate over signal specifications for allowed services
Args:
signal_names (list): allowed signals
Yields:
specification (dict): Specification for a signal for an allowed service
"""
for _, spec in self._generator(signal_names, unique_names=False):
yield spec
def _properties_service_model(self, signal_names, accessors):
""" Placeholder
"""
properties = []
endpoint_members = {}
endpoint_types = {}
for signal_spec in self._specifications(signal_names):
interface = signal_spec[self.position.api.value]
if self.skip_interface(interface):
continue
endpoint = signal_spec[self.position.endpoint.value]
primitive = signal_spec[self.position.property_name.value]
if endpoint not in endpoint_members and primitive is not None:
endpoint_members[endpoint] = []
if primitive is not None:
if endpoint not in endpoint_types:
endpoint_types[endpoint] = {
'name': endpoint,
'kind': 'struct',
'description': {
'brief': endpoint,
"full": "Generated from project without custom description"
},
'members': []
}
endpoint_members[endpoint].append({
'name': primitive,
'type': primitive,
})
endpoint_types[endpoint]['members'].append({
'name': primitive,
'type': signal_spec[self.position.property_type.value],
})
else:
primitive_type = signal_spec[self.position.property_type.value]
primitive_desc = signal_spec[self.position.description.value]
primitive_unit = signal_spec[self.position.unit.value]
properties.append(
{
'name': endpoint,
'type': primitive_type,
'unit': primitive_unit,
'accessors': accessors,
'description': {
'brief': endpoint,
'full': primitive_desc,
}
}
)
for endpoint_name in sorted(endpoint_members):
properties.append(
{
'name': endpoint_name,
'type': endpoint_name,
'unit': 'struct',
'accessors': accessors,
'description': {
'brief': endpoint_name,
"full": "Generated from project without custom description"},
}
)
return_types = []
for endpoint_name, endpoint_type in endpoint_types.items():
return_types.append(endpoint_type)
return properties, return_types
def skip_interface(self, interface):
""" Filter services not in list.
Args:
service (str): interface
Returns:
skip (bool): Skip this interface
"""
if self.filter is None:
LOGGER.debug('No interface filter. Allowing everyone.')
return False
if interface in self.filter:
LOGGER.debug('%s is in %s', interface, self.filter)
return False
return True