powertrain-build 1.13.1__py3-none-any.whl → 1.13.3.dev3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- powertrain_build/__init__.py +40 -40
- powertrain_build/__main__.py +6 -6
- powertrain_build/a2l.py +582 -582
- powertrain_build/a2l_merge.py +650 -650
- powertrain_build/a2l_templates.py +717 -717
- powertrain_build/build.py +985 -985
- powertrain_build/build_defs.py +309 -309
- powertrain_build/build_proj_config.py +690 -690
- powertrain_build/check_interface.py +575 -575
- powertrain_build/cli.py +141 -141
- powertrain_build/config.py +542 -542
- powertrain_build/core.py +395 -395
- powertrain_build/core_dummy.py +343 -343
- powertrain_build/create_conversion_table.py +73 -73
- powertrain_build/dids.py +916 -916
- powertrain_build/dummy.py +157 -157
- powertrain_build/dummy_spm.py +252 -252
- powertrain_build/environmentcheck.py +52 -52
- powertrain_build/ext_dbg.py +255 -255
- powertrain_build/ext_var.py +327 -327
- powertrain_build/feature_configs.py +301 -301
- powertrain_build/gen_allsysteminfo.py +227 -227
- powertrain_build/gen_label_split.py +449 -449
- powertrain_build/handcode_replacer.py +124 -124
- powertrain_build/html_report.py +133 -133
- powertrain_build/interface/__init__.py +4 -4
- powertrain_build/interface/application.py +511 -511
- powertrain_build/interface/base.py +500 -500
- powertrain_build/interface/csp_api.py +490 -490
- powertrain_build/interface/device_proxy.py +677 -677
- powertrain_build/interface/ems.py +67 -67
- powertrain_build/interface/export_global_vars.py +121 -121
- powertrain_build/interface/generate_adapters.py +132 -132
- powertrain_build/interface/generate_hi_interface.py +87 -87
- powertrain_build/interface/generate_service.py +69 -69
- powertrain_build/interface/generate_wrappers.py +147 -147
- powertrain_build/interface/generation_utils.py +142 -142
- powertrain_build/interface/hal.py +194 -194
- powertrain_build/interface/model_yaml_verification.py +348 -348
- powertrain_build/interface/service.py +296 -296
- powertrain_build/interface/simulink.py +249 -249
- powertrain_build/interface/update_call_sources.py +180 -180
- powertrain_build/interface/update_model_yaml.py +186 -186
- powertrain_build/interface/zone_controller.py +362 -362
- powertrain_build/lib/__init__.py +4 -4
- powertrain_build/lib/helper_functions.py +127 -127
- powertrain_build/lib/logger.py +55 -55
- powertrain_build/matlab_scripts/CodeGen/BuildAutomationPyBuild.m +78 -78
- powertrain_build/matlab_scripts/CodeGen/Generate_A2L.m +154 -154
- powertrain_build/matlab_scripts/CodeGen/generateTLUnit.m +239 -239
- powertrain_build/matlab_scripts/CodeGen/getAsilClassification.m +28 -28
- powertrain_build/matlab_scripts/CodeGen/modelConfiguredForTL.m +28 -28
- powertrain_build/matlab_scripts/CodeGen/moveDefOutports.m +88 -88
- powertrain_build/matlab_scripts/CodeGen/parseCalMeasData.m +410 -410
- powertrain_build/matlab_scripts/CodeGen/parseCoreIdentifiers.m +139 -139
- powertrain_build/matlab_scripts/CodeGen/parseDIDs.m +141 -141
- powertrain_build/matlab_scripts/CodeGen/parseInPorts.m +106 -106
- powertrain_build/matlab_scripts/CodeGen/parseIncludeConfigs.m +25 -25
- powertrain_build/matlab_scripts/CodeGen/parseModelInfo.m +38 -38
- powertrain_build/matlab_scripts/CodeGen/parseNVM.m +81 -81
- powertrain_build/matlab_scripts/CodeGen/parseOutPorts.m +120 -120
- powertrain_build/matlab_scripts/CodeGen/parsePreProcBlks.m +23 -23
- powertrain_build/matlab_scripts/CodeGen/struct2JSON.m +128 -128
- powertrain_build/matlab_scripts/CodeGen/updateCodeSwConfig.m +31 -31
- powertrain_build/matlab_scripts/Init_PyBuild.m +91 -91
- powertrain_build/matlab_scripts/__init__.py +2 -2
- powertrain_build/matlab_scripts/helperFunctions/Get_Full_Name.m +46 -46
- powertrain_build/matlab_scripts/helperFunctions/Get_SrcLines.m +12 -12
- powertrain_build/matlab_scripts/helperFunctions/Init_Models.m +78 -78
- powertrain_build/matlab_scripts/helperFunctions/Init_Projects.m +67 -67
- powertrain_build/matlab_scripts/helperFunctions/Read_Units.m +34 -34
- powertrain_build/matlab_scripts/helperFunctions/SetProjectTimeSamples.m +26 -26
- powertrain_build/matlab_scripts/helperFunctions/Strip_Suffix.m +16 -16
- powertrain_build/matlab_scripts/helperFunctions/followLink.m +118 -118
- powertrain_build/matlab_scripts/helperFunctions/getCodeSwitches.m +50 -50
- powertrain_build/matlab_scripts/helperFunctions/getConsumerBlocks.m +30 -30
- powertrain_build/matlab_scripts/helperFunctions/getDefBlock.m +39 -39
- powertrain_build/matlab_scripts/helperFunctions/getDefOutport.m +58 -58
- powertrain_build/matlab_scripts/helperFunctions/getDstBlocks.m +19 -19
- powertrain_build/matlab_scripts/helperFunctions/getDstLines.m +13 -13
- powertrain_build/matlab_scripts/helperFunctions/getInterfaceSignals.m +37 -37
- powertrain_build/matlab_scripts/helperFunctions/getName.m +37 -37
- powertrain_build/matlab_scripts/helperFunctions/getPath.m +6 -6
- powertrain_build/matlab_scripts/helperFunctions/getProperValue.m +21 -21
- powertrain_build/matlab_scripts/helperFunctions/getSrcBlocks.m +19 -19
- powertrain_build/matlab_scripts/helperFunctions/getSrcLines.m +13 -13
- powertrain_build/matlab_scripts/helperFunctions/loadLibraries.m +10 -10
- powertrain_build/matlab_scripts/helperFunctions/loadjson.m +6 -6
- powertrain_build/matlab_scripts/helperFunctions/modifyEnumStructField.m +21 -21
- powertrain_build/matlab_scripts/helperFunctions/removeConfigDuplicates.m +31 -31
- powertrain_build/matlab_scripts/helperFunctions/sortSystemByClass.m +26 -26
- powertrain_build/matlab_scripts/helperFunctions/tl_getfast.m +89 -89
- powertrain_build/matlab_scripts/helperFunctions/topLevelSystem.m +20 -20
- powertrain_build/matlab_scripts/helperFunctions/updateModels.m +131 -131
- powertrain_build/memory_section.py +224 -224
- powertrain_build/nvm_def.py +729 -729
- powertrain_build/problem_logger.py +86 -86
- powertrain_build/pt_matlab.py +430 -430
- powertrain_build/pt_win32.py +144 -144
- powertrain_build/replace_compu_tab_ref.py +105 -105
- powertrain_build/rte_dummy.py +254 -254
- powertrain_build/sched_funcs.py +209 -207
- powertrain_build/signal.py +7 -7
- powertrain_build/signal_if_html_rep.py +221 -221
- powertrain_build/signal_if_html_rep_all.py +302 -302
- powertrain_build/signal_incons_html_rep.py +180 -180
- powertrain_build/signal_incons_html_rep_all.py +366 -366
- powertrain_build/signal_incons_html_rep_base.py +168 -168
- powertrain_build/signal_inconsistency_check.py +641 -641
- powertrain_build/signal_interfaces.py +864 -864
- powertrain_build/templates/Index_SigCheck_All.html +22 -22
- powertrain_build/templates/Index_SigIf_All.html +19 -19
- powertrain_build/types.py +218 -218
- powertrain_build/unit_configs.py +419 -419
- powertrain_build/user_defined_types.py +660 -660
- powertrain_build/versioncheck.py +66 -66
- powertrain_build/wrapper.py +512 -512
- powertrain_build/xlrd_csv.py +87 -87
- powertrain_build/zone_controller/__init__.py +4 -4
- powertrain_build/zone_controller/calibration.py +176 -176
- powertrain_build/zone_controller/composition_yaml.py +880 -878
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/METADATA +100 -100
- powertrain_build-1.13.3.dev3.dist-info/RECORD +130 -0
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/WHEEL +1 -1
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/LICENSE +202 -202
- powertrain_build-1.13.3.dev3.dist-info/pbr.json +1 -0
- powertrain_build-1.13.1.dist-info/RECORD +0 -130
- powertrain_build-1.13.1.dist-info/pbr.json +0 -1
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/entry_points.txt +0 -0
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/AUTHORS +0 -0
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/NOTICE +0 -0
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/top_level.txt +0 -0
|
@@ -1,660 +1,660 @@
|
|
|
1
|
-
# Copyright 2024 Volvo Car Corporation
|
|
2
|
-
# Licensed under Apache 2.0.
|
|
3
|
-
|
|
4
|
-
# -*- coding: utf-8 -*-
|
|
5
|
-
"""Module for handling user defined data types, such as enumerations."""
|
|
6
|
-
|
|
7
|
-
import re
|
|
8
|
-
import time
|
|
9
|
-
from ruamel.yaml import YAML
|
|
10
|
-
from copy import deepcopy
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from powertrain_build.build_proj_config import BuildProjConfig
|
|
13
|
-
from powertrain_build.problem_logger import ProblemLogger
|
|
14
|
-
from powertrain_build.unit_configs import UnitConfigs
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class UserDefinedTypes(ProblemLogger):
|
|
18
|
-
"""A class for accessing the project’s user defined data types (see :doc:`user_defined_types`)."""
|
|
19
|
-
|
|
20
|
-
FILE_PREFIXES = ['udt_'] # Add more as needed
|
|
21
|
-
UNDERLYING_DATA_TYPES = { # Add more as needed
|
|
22
|
-
'target_link': {
|
|
23
|
-
'u16': 'UInt16',
|
|
24
|
-
'i16': 'Int16',
|
|
25
|
-
'u8': 'UInt8',
|
|
26
|
-
'i8': 'Int8'
|
|
27
|
-
},
|
|
28
|
-
'embedded_coder': {
|
|
29
|
-
'u16': 'uint16_T',
|
|
30
|
-
'i16': 'int16_T',
|
|
31
|
-
'u8': 'uint8_T',
|
|
32
|
-
'i8': 'int8_T'
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
SIMULINK_DATA_TYPES = { # Add more as needed
|
|
36
|
-
'target_link': {
|
|
37
|
-
'Simulink.IntEnumType': 'Int32',
|
|
38
|
-
'int32': 'Int32',
|
|
39
|
-
'uint16': 'UInt16',
|
|
40
|
-
'int16': 'Int16',
|
|
41
|
-
'uint8': 'UInt8',
|
|
42
|
-
'int8': 'Int8'
|
|
43
|
-
},
|
|
44
|
-
'embedded_coder': {
|
|
45
|
-
'Simulink.IntEnumType': 'int32_T',
|
|
46
|
-
'int32': 'int32_T',
|
|
47
|
-
'uint16': 'uint16_T',
|
|
48
|
-
'int16': 'int16_T',
|
|
49
|
-
'uint8': 'uint8_T',
|
|
50
|
-
'int8': 'int8_T'
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
def __init__(self, build_prj_config, unit_configs):
|
|
55
|
-
"""Class Initialization.
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
build_prj_config (BuildProjConfig): Instance holding information of where to find units configs to parse.
|
|
59
|
-
unit_configs (UnitConfigs): Unit definitions.
|
|
60
|
-
"""
|
|
61
|
-
super().__init__()
|
|
62
|
-
if not isinstance(build_prj_config, BuildProjConfig) or not isinstance(unit_configs, UnitConfigs):
|
|
63
|
-
err = (
|
|
64
|
-
'Input arguments should be an instance of:'
|
|
65
|
-
f'BuildProjConfig, not {type(build_prj_config)}'
|
|
66
|
-
f'AND/OR UnitConfigs, not {type(unit_configs)}'
|
|
67
|
-
)
|
|
68
|
-
raise TypeError(err)
|
|
69
|
-
|
|
70
|
-
start_time = time.time()
|
|
71
|
-
self.info(' Start parsing files with user defined data types')
|
|
72
|
-
self._build_prj_cfg = build_prj_config
|
|
73
|
-
self._unit_configs = unit_configs
|
|
74
|
-
self.enums_per_unit = {}
|
|
75
|
-
self.common_enums = self._read_enums_from_definitions()
|
|
76
|
-
self.structs_per_unit = {}
|
|
77
|
-
self._parse_all_user_defined_types()
|
|
78
|
-
self.common_header_files = []
|
|
79
|
-
# Must run last to be able to compare with TL/EC data types
|
|
80
|
-
self.all_enums = self._get_enumerations()
|
|
81
|
-
self._interface_data_types = self._parse_interface_data_types()
|
|
82
|
-
self.info(' Finished parsing files with user defined data types (in %4.2f s)', time.time() - start_time)
|
|
83
|
-
|
|
84
|
-
@staticmethod
|
|
85
|
-
def convert_interface_enum_to_simulink(interface_enum, underlying_data_type=None):
|
|
86
|
-
""" Given an interface enumeration, convert it to a simulink parsed version.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
interface_enum ([dict]): Interface enumeration definition.
|
|
90
|
-
underlying_data_type (str): Underlying data type, usually not available for interface enumerations.
|
|
91
|
-
Returns
|
|
92
|
-
simulink_enum (dict): Simulink enumeration definition.
|
|
93
|
-
"""
|
|
94
|
-
members = {}
|
|
95
|
-
default_value = None
|
|
96
|
-
# Assuming interface enumerations start at 0 counting upwards
|
|
97
|
-
for idx, enum_member in enumerate(interface_enum):
|
|
98
|
-
if 'default' in enum_member:
|
|
99
|
-
default_value = enum_member['in']
|
|
100
|
-
else:
|
|
101
|
-
members[enum_member['in']] = idx
|
|
102
|
-
simulink_enum = {
|
|
103
|
-
'underlying_data_type': underlying_data_type,
|
|
104
|
-
'members': members,
|
|
105
|
-
'default_value': default_value
|
|
106
|
-
}
|
|
107
|
-
return simulink_enum
|
|
108
|
-
|
|
109
|
-
def _parse_interface_data_types(self):
|
|
110
|
-
""" Get interface data types.
|
|
111
|
-
Also, runs validation tests.
|
|
112
|
-
|
|
113
|
-
Returns:
|
|
114
|
-
interface_data_types (dict): Specification interface data types.
|
|
115
|
-
"""
|
|
116
|
-
interface_data_types = {}
|
|
117
|
-
interface_data_types_path = Path(self._build_prj_cfg.get_root_dir(), 'conf.local', 'interface_data_types.yml')
|
|
118
|
-
|
|
119
|
-
if not interface_data_types_path.is_file():
|
|
120
|
-
self.warning('Cannot extract interface data types. File not found: %s', str(interface_data_types_path))
|
|
121
|
-
return interface_data_types
|
|
122
|
-
|
|
123
|
-
self.info(' Parsing file: %s', str(interface_data_types_path))
|
|
124
|
-
with interface_data_types_path.open(mode='r', encoding='utf-8') as fh:
|
|
125
|
-
yaml = YAML(typ='safe', pure=True)
|
|
126
|
-
interface_data_types_yaml = yaml.load(fh)
|
|
127
|
-
interface_data_types_tmp = interface_data_types_yaml['types']
|
|
128
|
-
# Add more as needed
|
|
129
|
-
if 'enums' in interface_data_types_tmp:
|
|
130
|
-
valid_interface_enumerations = self._validate_interface_enumerations(interface_data_types_tmp['enums'])
|
|
131
|
-
interface_data_types['enums'] = valid_interface_enumerations
|
|
132
|
-
return interface_data_types
|
|
133
|
-
|
|
134
|
-
def _parse_all_user_defined_types(self):
|
|
135
|
-
"""Parse all files containing user defined data types."""
|
|
136
|
-
src_dirs = self._build_prj_cfg.get_unit_src_dirs()
|
|
137
|
-
for unit, src_dir in src_dirs.items():
|
|
138
|
-
self._parse_unit_user_defined_types(unit, src_dir)
|
|
139
|
-
|
|
140
|
-
def _parse_unit_user_defined_types(self, unit, unit_src_dir):
|
|
141
|
-
"""Parse unit defined types for a given unit.
|
|
142
|
-
|
|
143
|
-
Files to parse are found using expected prefixes, see FILE_PREFIXES.
|
|
144
|
-
Data types to parse can be added as needed.
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
unit (str): Current unit/model name.
|
|
148
|
-
unit_src_dir (str): Unit sourcecode directory.
|
|
149
|
-
"""
|
|
150
|
-
self.enums_per_unit[unit] = {}
|
|
151
|
-
self.structs_per_unit[unit] = {}
|
|
152
|
-
found_files = [Path(unit_src_dir, unit.split('__')[0] + '.h')]
|
|
153
|
-
for file_prefix in self.FILE_PREFIXES:
|
|
154
|
-
found_files.extend(Path(unit_src_dir).glob(file_prefix + '*.h'))
|
|
155
|
-
for found_file in found_files:
|
|
156
|
-
self.debug(' Parsing file: %s', str(found_file))
|
|
157
|
-
self._parse_target_link_enum(unit, found_file)
|
|
158
|
-
self._parse_target_link_struct(unit, found_file)
|
|
159
|
-
self._validate_project_enumerations()
|
|
160
|
-
self._validate_project_structs()
|
|
161
|
-
|
|
162
|
-
def _parse_target_link_struct(self, unit, header_file: Path):
|
|
163
|
-
"""Parse structs generated by TargetLink, given a header file.
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
unit (str): Current unit/model name.
|
|
167
|
-
header_file (Path): Header file to parse.
|
|
168
|
-
"""
|
|
169
|
-
with header_file.open(mode='r', encoding='ISO-8859-1') as hf_fh:
|
|
170
|
-
header_file_content = hf_fh.read()
|
|
171
|
-
structs = re.findall(
|
|
172
|
-
r'^struct ([A-Za-z0-9_]+) {\s*\n'
|
|
173
|
-
r'((?:\s*\w+ \w+;\s*\n)*)'
|
|
174
|
-
r'};',
|
|
175
|
-
header_file_content,
|
|
176
|
-
flags=re.M
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
for struct_name, struct_content in structs:
|
|
180
|
-
struct_members = re.findall(r'\s*(\w+) (\w+);', struct_content, flags=re.M)
|
|
181
|
-
struct_dict = {
|
|
182
|
-
'members': {variable: data_type for data_type, variable in struct_members}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if struct_name in self.structs_per_unit[unit]:
|
|
186
|
-
if self.structs_per_unit[unit][struct_name]['members'] == struct_dict['members']:
|
|
187
|
-
self.info('Struct %s is multiply defined in %s, although they are consistent.', struct_name, unit)
|
|
188
|
-
else:
|
|
189
|
-
self.critical('Found inconsistent multiply defined struct: %s, units: %s.', struct_name, [unit])
|
|
190
|
-
else:
|
|
191
|
-
self.structs_per_unit[unit][struct_name] = struct_dict.copy()
|
|
192
|
-
|
|
193
|
-
def _validate_project_structs(self):
|
|
194
|
-
"""Checks for inconsistencies in struct definitions for all units in the project."""
|
|
195
|
-
structs = {}
|
|
196
|
-
for unit, struct_names in self.structs_per_unit.items():
|
|
197
|
-
for struct_name, struct_data in struct_names.items():
|
|
198
|
-
if struct_name in structs:
|
|
199
|
-
structs[struct_name]['units'].append(unit)
|
|
200
|
-
if not structs[struct_name]['members'] == struct_data['members']:
|
|
201
|
-
self.critical(
|
|
202
|
-
'Found inconsistent multiply defined struct: %s, units: %s.',
|
|
203
|
-
struct_name,
|
|
204
|
-
structs[struct_name]['units']
|
|
205
|
-
)
|
|
206
|
-
else:
|
|
207
|
-
structs[struct_name] = struct_data.copy()
|
|
208
|
-
structs[struct_name]['units'] = [unit]
|
|
209
|
-
|
|
210
|
-
def _validate_interface_enumerations(self, interface_enumerations):
|
|
211
|
-
""" Given a specification for enumerations
|
|
212
|
-
|
|
213
|
-
Args:
|
|
214
|
-
interface_enumerations (dict): Interface enumerations.
|
|
215
|
-
Returns
|
|
216
|
-
valid_interface_enumerations (dict): Specification, valid interface enumerations.
|
|
217
|
-
"""
|
|
218
|
-
valid_interface_enumerations = {}
|
|
219
|
-
for enum_name, enum_data in interface_enumerations.items():
|
|
220
|
-
if enum_name in valid_interface_enumerations:
|
|
221
|
-
self.critical('%s is multiply defined in interface enumeration definitions.', enum_name)
|
|
222
|
-
elif enum_name not in self.all_enums:
|
|
223
|
-
self.critical('%s is not defined in the project', enum_name)
|
|
224
|
-
else:
|
|
225
|
-
converted = self.convert_interface_enum_to_simulink(
|
|
226
|
-
enum_data,
|
|
227
|
-
self.all_enums[enum_name]['underlying_data_type']
|
|
228
|
-
)
|
|
229
|
-
is_consistent = self._compare_enum_definitions(
|
|
230
|
-
self.all_enums[enum_name]['units'],
|
|
231
|
-
enum_name,
|
|
232
|
-
converted,
|
|
233
|
-
self.all_enums[enum_name]
|
|
234
|
-
)
|
|
235
|
-
if is_consistent:
|
|
236
|
-
valid_interface_enumerations[enum_name] = enum_data
|
|
237
|
-
return valid_interface_enumerations
|
|
238
|
-
|
|
239
|
-
def _validate_project_enumerations(self):
|
|
240
|
-
"""Checks for inconsistencies in enum definitions for all units in the project."""
|
|
241
|
-
enumerations = {}
|
|
242
|
-
for unit, enum_names in self.enums_per_unit.items():
|
|
243
|
-
for enum_name, enum_data in enum_names.items():
|
|
244
|
-
if enum_name in enumerations:
|
|
245
|
-
enumerations[enum_name]['units'].append(unit)
|
|
246
|
-
self._compare_enum_definitions(
|
|
247
|
-
enumerations[enum_name]['units'],
|
|
248
|
-
enum_name,
|
|
249
|
-
enumerations[enum_name],
|
|
250
|
-
enum_data
|
|
251
|
-
)
|
|
252
|
-
else:
|
|
253
|
-
enumerations[enum_name] = enum_data.copy()
|
|
254
|
-
enumerations[enum_name]['units'] = [unit]
|
|
255
|
-
|
|
256
|
-
def _compare_enum_definitions(self, units, enum_name, enum_one, enum_two):
|
|
257
|
-
"""Compare two enumeration definitions.
|
|
258
|
-
|
|
259
|
-
Args:
|
|
260
|
-
units ([str]): List of units using given enumerations.
|
|
261
|
-
enum_name (str): Name of enumeration.
|
|
262
|
-
enum_one (dict): Enumeration one to compare.
|
|
263
|
-
enum_two (dict): Enumeration two to compare.
|
|
264
|
-
Returns:
|
|
265
|
-
is_consistent_enums (bool): True/False depending on enum consistency.
|
|
266
|
-
"""
|
|
267
|
-
is_consistent_enums = True
|
|
268
|
-
error_message = f'Found inconsistent multiply defined enumeration: {enum_name}, units: {units}.\n'
|
|
269
|
-
if enum_one['underlying_data_type'] != enum_two['underlying_data_type']:
|
|
270
|
-
is_consistent_enums = False
|
|
271
|
-
error_message += (
|
|
272
|
-
'underlying_data_type differs: '
|
|
273
|
-
f"{enum_one['underlying_data_type']} vs. {enum_two['underlying_data_type']}.\n"
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
enum_one_members = list(enum_one['members'].items())
|
|
277
|
-
enum_two_members = list(enum_two['members'].items())
|
|
278
|
-
if len(enum_one_members) != len(enum_two_members):
|
|
279
|
-
is_consistent_enums = False
|
|
280
|
-
error_message += (
|
|
281
|
-
f'Number of enum members differs: {len(enum_one_members)} vs. {len(enum_two_members)}.\n'
|
|
282
|
-
)
|
|
283
|
-
else:
|
|
284
|
-
for idx, enum_one_member in enumerate(enum_one_members):
|
|
285
|
-
if enum_one_member[0] != enum_two_members[idx][0] or enum_one_member[1] != enum_two_members[idx][1]:
|
|
286
|
-
is_consistent_enums = False
|
|
287
|
-
error_message += (
|
|
288
|
-
f'Enum member definition differs: {enum_one_members[idx]} vs. {enum_two_members[idx]}.\n'
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
if enum_one['default_value'] != enum_two['default_value']:
|
|
292
|
-
is_consistent_enums = False
|
|
293
|
-
error_message += (
|
|
294
|
-
'default_value differs: '
|
|
295
|
-
f"{enum_one['default_value']} vs. {enum_two['default_value']}.\n"
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
if not is_consistent_enums:
|
|
299
|
-
self.critical(error_message)
|
|
300
|
-
|
|
301
|
-
return is_consistent_enums
|
|
302
|
-
|
|
303
|
-
def _parse_target_link_enum(self, unit, header_file: Path):
|
|
304
|
-
"""Parse enumerations generated by TargetLink, given a header file.
|
|
305
|
-
|
|
306
|
-
Args:
|
|
307
|
-
unit (str): Current unit/model name.
|
|
308
|
-
header_file (Path): Header file to parse.
|
|
309
|
-
"""
|
|
310
|
-
with header_file.open(mode='r', encoding='ISO-8859-1') as hf_fh:
|
|
311
|
-
header_file_content = hf_fh.read()
|
|
312
|
-
enums = re.findall(
|
|
313
|
-
r'^typedef enum ([A-Za-z0-9]+)_tag {\s*\n'
|
|
314
|
-
r'((?:\s*\w+ = [-+]?\d+,?\s*\n)*)'
|
|
315
|
-
r'} \1;',
|
|
316
|
-
header_file_content,
|
|
317
|
-
flags=re.M
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
for enum_name, enum_content in enums:
|
|
321
|
-
enum_members_tmp = re.findall(r'\s*(\w+) = ([-+]?\d+)', enum_content, flags=re.M)
|
|
322
|
-
enum_members = [(k, int(v)) for k, v in enum_members_tmp]
|
|
323
|
-
if enum_name in self.common_enums:
|
|
324
|
-
underlying_data_type = self.common_enums[enum_name]['underlying_data_type']
|
|
325
|
-
else:
|
|
326
|
-
self.warning('Calculating underlying data type for: %s', str(enum_name))
|
|
327
|
-
underlying_data_type = self._calculate_underlying_data_type(unit, enum_name, enum_members)
|
|
328
|
-
enum_dict = {
|
|
329
|
-
'underlying_data_type': underlying_data_type,
|
|
330
|
-
'members': {},
|
|
331
|
-
'default_value': self.get_default_enum_value(unit, enum_name)
|
|
332
|
-
}
|
|
333
|
-
for key, value in enum_members:
|
|
334
|
-
enum_dict['members'][key] = value
|
|
335
|
-
|
|
336
|
-
if enum_name in self.enums_per_unit[unit]:
|
|
337
|
-
is_consistent_enums = self._compare_enum_definitions(
|
|
338
|
-
[unit],
|
|
339
|
-
enum_name,
|
|
340
|
-
self.enums_per_unit[unit][enum_name],
|
|
341
|
-
enum_dict
|
|
342
|
-
)
|
|
343
|
-
if is_consistent_enums:
|
|
344
|
-
self.info(
|
|
345
|
-
'Enumeration %s is multiply defined in %s, although they are consistent.',
|
|
346
|
-
enum_name,
|
|
347
|
-
unit
|
|
348
|
-
)
|
|
349
|
-
else:
|
|
350
|
-
self.enums_per_unit[unit][enum_name] = enum_dict.copy()
|
|
351
|
-
|
|
352
|
-
def _calculate_underlying_data_type(self, unit, enum_name, enum_members):
|
|
353
|
-
"""Calculate best fitting data type given an enum definition.
|
|
354
|
-
|
|
355
|
-
Args:
|
|
356
|
-
unit (str): Current unit/model name.
|
|
357
|
-
enum_name (str): Current enum name.
|
|
358
|
-
enum_members (list(tuple)): List of enum members, name value pairs.
|
|
359
|
-
Returns:
|
|
360
|
-
underlying_data_type (str): Best fitting data type.
|
|
361
|
-
"""
|
|
362
|
-
code_generator = self._unit_configs.get_unit_code_generator(unit)
|
|
363
|
-
enum_member_values = [member_value for member_name, member_value in enum_members]
|
|
364
|
-
min_value = min(enum_member_values)
|
|
365
|
-
max_value = max(enum_member_values)
|
|
366
|
-
|
|
367
|
-
# TODO Consider forcing signed like CS (or ARXML, from database?) does, seems to be int8 specifically
|
|
368
|
-
underlying_data_type = None
|
|
369
|
-
if min_value >= 0:
|
|
370
|
-
if max_value <= 255:
|
|
371
|
-
underlying_data_type = self.UNDERLYING_DATA_TYPES[code_generator]['u8']
|
|
372
|
-
elif max_value <= 65535:
|
|
373
|
-
underlying_data_type = self.UNDERLYING_DATA_TYPES[code_generator]['u16']
|
|
374
|
-
elif min_value >= -128:
|
|
375
|
-
if max_value <= 127:
|
|
376
|
-
underlying_data_type = self.UNDERLYING_DATA_TYPES[code_generator]['i8']
|
|
377
|
-
elif max_value <= 32767:
|
|
378
|
-
underlying_data_type = self.UNDERLYING_DATA_TYPES[code_generator]['i16']
|
|
379
|
-
elif min_value >= -32768:
|
|
380
|
-
if max_value <= 32767:
|
|
381
|
-
underlying_data_type = self.UNDERLYING_DATA_TYPES[code_generator]['i16']
|
|
382
|
-
|
|
383
|
-
if underlying_data_type is None:
|
|
384
|
-
self.critical(
|
|
385
|
-
'Unhandled enum size, name: %s, min: %s, max: %s. Valid types are uint8/16 and int8/16',
|
|
386
|
-
enum_name,
|
|
387
|
-
min_value,
|
|
388
|
-
max_value
|
|
389
|
-
)
|
|
390
|
-
|
|
391
|
-
return underlying_data_type
|
|
392
|
-
|
|
393
|
-
def _read_enums_from_definitions(self):
|
|
394
|
-
""" Reads all enums from .m files where they are defined. Requires projectInfo.enumDefDir in
|
|
395
|
-
ProjectCfg.json file.
|
|
396
|
-
Files defining simulink class enumerations are expected to follow a template.
|
|
397
|
-
|
|
398
|
-
Returns:
|
|
399
|
-
common_enums (dict): Dictionary containing all enums defined in the enum definition directory.
|
|
400
|
-
"""
|
|
401
|
-
common_enums = {}
|
|
402
|
-
if self._build_prj_cfg.get_enum_def_dir() is None:
|
|
403
|
-
self.warning(
|
|
404
|
-
'Cannot parse enumerations from .m files. Missing "enumDefDir" in project config.'
|
|
405
|
-
)
|
|
406
|
-
return {}
|
|
407
|
-
|
|
408
|
-
enum_files = Path(self._build_prj_cfg.get_enum_def_dir()).rglob('*.m')
|
|
409
|
-
for enum_file in enum_files:
|
|
410
|
-
with enum_file.open(mode='r', encoding='ISO-8859-1') as fh:
|
|
411
|
-
enum_file_content = fh.read()
|
|
412
|
-
enum_name = enum_file.stem
|
|
413
|
-
|
|
414
|
-
members_part = re.search(
|
|
415
|
-
r'\s+enumeration\s*\n'
|
|
416
|
-
r'(\s*%.*|\s*\w+\s*\(\d+\)\s*(%.*)?\n)+'
|
|
417
|
-
r'\s+end',
|
|
418
|
-
enum_file_content,
|
|
419
|
-
flags=re.MULTILINE
|
|
420
|
-
)
|
|
421
|
-
if not members_part:
|
|
422
|
-
self.warning('Cannot extract enumeration members from %s', str(enum_file))
|
|
423
|
-
continue
|
|
424
|
-
members = re.findall(r'(\w+)\s*\((\d+)\)', members_part.group(0))
|
|
425
|
-
member_dict = {enum_name + '_' + member[0]: int(member[1]) for member in members}
|
|
426
|
-
enum_members = [(member_name, member_value) for member_name, member_value in member_dict.items()]
|
|
427
|
-
|
|
428
|
-
get_default_value = re.search(
|
|
429
|
-
r'function ([A-Za-z0-9]+) = getDefaultValue\(\)[\s\n]*'
|
|
430
|
-
r'\1 = [A-Za-z0-9]+\.([A-Za-z0-9]+);[\s\n]*'
|
|
431
|
-
r'end',
|
|
432
|
-
enum_file_content,
|
|
433
|
-
flags=re.MULTILINE
|
|
434
|
-
)
|
|
435
|
-
if get_default_value is None:
|
|
436
|
-
self.warning('Cannot extract default enumeration values in: %s', str(enum_file))
|
|
437
|
-
continue
|
|
438
|
-
default_value = get_default_value.groups()[1]
|
|
439
|
-
default_member = f'{enum_name}_{default_value}'
|
|
440
|
-
|
|
441
|
-
calculate_underlying_data_type = re.search(
|
|
442
|
-
r'function ([A-Za-z0-9]+) = calculateUnderlyingDataType\(\)[\s\n]*'
|
|
443
|
-
r'\1 = (true|false);[\s\n]*'
|
|
444
|
-
r'end',
|
|
445
|
-
enum_file_content,
|
|
446
|
-
flags=re.MULTILINE
|
|
447
|
-
)
|
|
448
|
-
get_storage_type = re.search(
|
|
449
|
-
r'classdef[\s]+[A-Za-z0-9]+[\s]*<[\s]*([A-Za-z0-9\.]+)[\s\n]*',
|
|
450
|
-
enum_file_content,
|
|
451
|
-
flags=re.MULTILINE
|
|
452
|
-
)
|
|
453
|
-
if get_storage_type is None:
|
|
454
|
-
self.warning('Cannot extract storage type in: %s', str(enum_file))
|
|
455
|
-
if calculate_underlying_data_type is None or calculate_underlying_data_type.groups()[1] == 'false':
|
|
456
|
-
self.warning('Calculating underlying data type for: %s', str(enum_file))
|
|
457
|
-
underlying_data_type = self._calculate_underlying_data_type('target_link', enum_name, enum_members)
|
|
458
|
-
else:
|
|
459
|
-
underlying_data_type = self._calculate_underlying_data_type('target_link', enum_name, enum_members)
|
|
460
|
-
else:
|
|
461
|
-
underlying_data_type = self.SIMULINK_DATA_TYPES['target_link'][get_storage_type.groups()[0]]
|
|
462
|
-
|
|
463
|
-
common_enums[enum_name] = {
|
|
464
|
-
"underlying_data_type": underlying_data_type,
|
|
465
|
-
"default_value": default_member,
|
|
466
|
-
"members": member_dict
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
return common_enums
|
|
470
|
-
|
|
471
|
-
def _get_enumerations(self):
|
|
472
|
-
"""Get all enumeration defined in the project, together with unit usage.
|
|
473
|
-
|
|
474
|
-
Information already provided in self._validate_project_enumerations during initialization.
|
|
475
|
-
|
|
476
|
-
Returns:
|
|
477
|
-
enumerations (dict): Enumerations defined in the projects, including unit usage.
|
|
478
|
-
"""
|
|
479
|
-
enumerations = {}
|
|
480
|
-
for unit, enum_names in self.enums_per_unit.items():
|
|
481
|
-
for enum_name, enum_data in enum_names.items():
|
|
482
|
-
if enum_name in enumerations:
|
|
483
|
-
enumerations[enum_name]['units'].append(unit)
|
|
484
|
-
else:
|
|
485
|
-
enumerations[enum_name] = deepcopy(enum_data)
|
|
486
|
-
enumerations[enum_name]['units'] = [unit]
|
|
487
|
-
if self._build_prj_cfg.get_code_generation_config("includeAllEnums"):
|
|
488
|
-
for enum_name, enum_data in self.common_enums.items():
|
|
489
|
-
if enum_name not in enumerations:
|
|
490
|
-
self.warning(
|
|
491
|
-
"Enumeration %s is not used in any unit. Included since 'includeAllEnums' is set in config.",
|
|
492
|
-
enum_name
|
|
493
|
-
)
|
|
494
|
-
enumerations[enum_name] = deepcopy(enum_data)
|
|
495
|
-
enumerations[enum_name]['units'] = []
|
|
496
|
-
return enumerations
|
|
497
|
-
|
|
498
|
-
def get_default_enum_value(self, unit, enum_name):
|
|
499
|
-
"""Get default value of given enumeration name by searching in the unit configuration.
|
|
500
|
-
|
|
501
|
-
Args:
|
|
502
|
-
unit (str): Current unit/model name.
|
|
503
|
-
enum_name (str): Name of an enumeration.
|
|
504
|
-
Returns:
|
|
505
|
-
default_value_str (str): Default value (string) for the given enumeration name.
|
|
506
|
-
None if value could not be extracted.
|
|
507
|
-
"""
|
|
508
|
-
# The config file is generated in one go, stop when first occurrence is found, cannot differ
|
|
509
|
-
per_unit_cfg = self._unit_configs.get_per_unit_cfg()
|
|
510
|
-
if unit in per_unit_cfg:
|
|
511
|
-
u_cfg = per_unit_cfg[unit]
|
|
512
|
-
else:
|
|
513
|
-
# Should not happen
|
|
514
|
-
self.warning(
|
|
515
|
-
'Cannot extract default enumeration value for %s in %s. Unit is missing in the unit configuration.',
|
|
516
|
-
enum_name,
|
|
517
|
-
unit
|
|
518
|
-
)
|
|
519
|
-
return None
|
|
520
|
-
|
|
521
|
-
for signal_type in ['inports', 'outports', 'local_vars', 'calib_consts']:
|
|
522
|
-
for signal_data in u_cfg[signal_type].values():
|
|
523
|
-
if enum_name == signal_data['type'] and 'default' in signal_data:
|
|
524
|
-
return f"{enum_name.upper()}_{signal_data['default'].upper()}"
|
|
525
|
-
|
|
526
|
-
self.warning(
|
|
527
|
-
'Cannot extract default enumeration value for %s in %s. '
|
|
528
|
-
'Either the enumeration or its default value is missing in the unit configuration file. ',
|
|
529
|
-
enum_name,
|
|
530
|
-
unit
|
|
531
|
-
)
|
|
532
|
-
common_default = self.common_enums.get(enum_name, {}).get('default_value', None)
|
|
533
|
-
if common_default is not None:
|
|
534
|
-
return common_default.upper()
|
|
535
|
-
return None
|
|
536
|
-
|
|
537
|
-
def get_enumerations(self):
|
|
538
|
-
"""Get all enumeration defined in the project, together with unit usage.
|
|
539
|
-
|
|
540
|
-
Returns:
|
|
541
|
-
self.all_enums (dict): Enumerations defined in the projects, including unit usage.
|
|
542
|
-
"""
|
|
543
|
-
return self.all_enums
|
|
544
|
-
|
|
545
|
-
def get_interface_data_types(self):
|
|
546
|
-
"""Returns all interface data types"""
|
|
547
|
-
return deepcopy(self._interface_data_types)
|
|
548
|
-
|
|
549
|
-
def get_structs(self):
|
|
550
|
-
"""Get all structs defined in the project, together with unit usage.
|
|
551
|
-
|
|
552
|
-
Information already provided in self._validate_project_structs during initialization.
|
|
553
|
-
|
|
554
|
-
Returns:
|
|
555
|
-
structs (dict): Structs defined in the projects, including unit usage.
|
|
556
|
-
"""
|
|
557
|
-
structs = {}
|
|
558
|
-
for unit, struct_names in self.structs_per_unit.items():
|
|
559
|
-
for struct_name, struct_data in struct_names.items():
|
|
560
|
-
if struct_name in structs:
|
|
561
|
-
structs[struct_name]['units'].append(unit)
|
|
562
|
-
else:
|
|
563
|
-
structs[struct_name] = deepcopy(struct_data)
|
|
564
|
-
structs[struct_name]['units'] = [unit]
|
|
565
|
-
return structs
|
|
566
|
-
|
|
567
|
-
def _get_header_file_header(self, guard: str):
|
|
568
|
-
"""Get header for common header files.
|
|
569
|
-
|
|
570
|
-
Args:
|
|
571
|
-
guard (str): Guard for common header files.
|
|
572
|
-
Returns:
|
|
573
|
-
header (str): Header for common header files.
|
|
574
|
-
"""
|
|
575
|
-
return (
|
|
576
|
-
f'#ifndef {guard}\n'
|
|
577
|
-
f'#define {guard}\n'
|
|
578
|
-
f'{self._unit_configs.base_types_headers}'
|
|
579
|
-
)
|
|
580
|
-
|
|
581
|
-
def _generate_struct_header_file(self, file_path: Path):
|
|
582
|
-
""" Generates a header file declaring all TargetLink structs used in the project.
|
|
583
|
-
|
|
584
|
-
Args:
|
|
585
|
-
file_path (Path): Path to file to generate.
|
|
586
|
-
"""
|
|
587
|
-
structs = self.get_structs()
|
|
588
|
-
guard = f"{file_path.stem.upper()}_H"
|
|
589
|
-
with file_path.open('w') as fh:
|
|
590
|
-
fh.write(self._get_header_file_header(guard))
|
|
591
|
-
fh.write('#include "VcEnumerations.h"\n') # struct members may be of enum type
|
|
592
|
-
fh.write('/* VCC Structs */\n')
|
|
593
|
-
for struct_name, struct_data in structs.items():
|
|
594
|
-
fh.write('\n')
|
|
595
|
-
fh.write(f'struct {struct_name} {{\n')
|
|
596
|
-
for variable, data_type in struct_data['members'].items():
|
|
597
|
-
fh.write(f" {data_type} {variable};\n")
|
|
598
|
-
fh.write('};\n')
|
|
599
|
-
fh.write(f'#endif /* {guard} */\n')
|
|
600
|
-
|
|
601
|
-
def _generate_enum_header_file(self, file_path: Path):
|
|
602
|
-
""" Generates a header file declaring all TargetLink enumerations used in the project.
|
|
603
|
-
|
|
604
|
-
Args:
|
|
605
|
-
file_path (Path): Path to file to generate.
|
|
606
|
-
"""
|
|
607
|
-
enumerations = self.get_enumerations()
|
|
608
|
-
rte_interface_enums = self._build_prj_cfg.get_code_generation_config("mapToRteEnums")
|
|
609
|
-
if not self._build_prj_cfg.get_enum_def_dir() and rte_interface_enums:
|
|
610
|
-
self.warning(
|
|
611
|
-
"No enum definition directory specified. Cannot generate all enums in decinition directory."
|
|
612
|
-
)
|
|
613
|
-
rte_interface_enums = False
|
|
614
|
-
guard = f"{file_path.stem.upper()}_H"
|
|
615
|
-
with file_path.open('w') as fh:
|
|
616
|
-
fh.write(self._get_header_file_header(guard))
|
|
617
|
-
if rte_interface_enums:
|
|
618
|
-
composition_name = self._build_prj_cfg.get_composition_config("softwareComponentName")
|
|
619
|
-
fh.write(f'#include "Rte_{ composition_name }_Type.h"\n')
|
|
620
|
-
fh.write('/* VCC Enumerations */\n')
|
|
621
|
-
for enum_name, enum_data in enumerations.items():
|
|
622
|
-
members = enum_data['members']
|
|
623
|
-
member_strs = ', \n'.join([f" {member.upper()} = {value}" for member, value in members.items()])
|
|
624
|
-
enum_definition = (
|
|
625
|
-
f'\n'
|
|
626
|
-
f'typedef enum {enum_name}_tag {{\n'
|
|
627
|
-
f'{member_strs} \n'
|
|
628
|
-
f'}} {enum_name};\n'
|
|
629
|
-
)
|
|
630
|
-
if rte_interface_enums:
|
|
631
|
-
enum = self.common_enums[enum_name]
|
|
632
|
-
one_member = next(iter(enum["members"]))
|
|
633
|
-
rte_translation = '\n'.join(
|
|
634
|
-
f"#define {member.upper()} {member}" for member in enum["members"].keys()
|
|
635
|
-
)
|
|
636
|
-
enum_definition = (
|
|
637
|
-
f'\n'
|
|
638
|
-
f'#ifndef {one_member}\n'
|
|
639
|
-
f'{enum_definition}\n'
|
|
640
|
-
f'#else\n'
|
|
641
|
-
f'{rte_translation}\n'
|
|
642
|
-
f'#endif /* {one_member} */\n'
|
|
643
|
-
)
|
|
644
|
-
fh.write(enum_definition)
|
|
645
|
-
fh.write(f'#endif /* {guard} */\n')
|
|
646
|
-
|
|
647
|
-
def generate_common_header_files(self):
|
|
648
|
-
""" Generates common header files.
|
|
649
|
-
|
|
650
|
-
These headers can be included in general files such as VcDummy_spm.h or VcExtVar.h.
|
|
651
|
-
|
|
652
|
-
Returns:
|
|
653
|
-
common_header_files (list(Path)): List of names of generated files.
|
|
654
|
-
"""
|
|
655
|
-
src_dir = self._build_prj_cfg.get_src_code_dst_dir()
|
|
656
|
-
enum_file_path = Path(src_dir, 'VcEnumerations.h')
|
|
657
|
-
struct_file_path = Path(src_dir, 'VcStructs.h')
|
|
658
|
-
self._generate_enum_header_file(enum_file_path)
|
|
659
|
-
self._generate_struct_header_file(struct_file_path)
|
|
660
|
-
self.common_header_files.extend([enum_file_path.name, struct_file_path.name])
|
|
1
|
+
# Copyright 2024 Volvo Car Corporation
|
|
2
|
+
# Licensed under Apache 2.0.
|
|
3
|
+
|
|
4
|
+
# -*- coding: utf-8 -*-
|
|
5
|
+
"""Module for handling user defined data types, such as enumerations."""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
import time
|
|
9
|
+
from ruamel.yaml import YAML
|
|
10
|
+
from copy import deepcopy
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from powertrain_build.build_proj_config import BuildProjConfig
|
|
13
|
+
from powertrain_build.problem_logger import ProblemLogger
|
|
14
|
+
from powertrain_build.unit_configs import UnitConfigs
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UserDefinedTypes(ProblemLogger):
|
|
18
|
+
"""A class for accessing the project’s user defined data types (see :doc:`user_defined_types`)."""
|
|
19
|
+
|
|
20
|
+
FILE_PREFIXES = ['udt_'] # Add more as needed
|
|
21
|
+
UNDERLYING_DATA_TYPES = { # Add more as needed
|
|
22
|
+
'target_link': {
|
|
23
|
+
'u16': 'UInt16',
|
|
24
|
+
'i16': 'Int16',
|
|
25
|
+
'u8': 'UInt8',
|
|
26
|
+
'i8': 'Int8'
|
|
27
|
+
},
|
|
28
|
+
'embedded_coder': {
|
|
29
|
+
'u16': 'uint16_T',
|
|
30
|
+
'i16': 'int16_T',
|
|
31
|
+
'u8': 'uint8_T',
|
|
32
|
+
'i8': 'int8_T'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
SIMULINK_DATA_TYPES = { # Add more as needed
|
|
36
|
+
'target_link': {
|
|
37
|
+
'Simulink.IntEnumType': 'Int32',
|
|
38
|
+
'int32': 'Int32',
|
|
39
|
+
'uint16': 'UInt16',
|
|
40
|
+
'int16': 'Int16',
|
|
41
|
+
'uint8': 'UInt8',
|
|
42
|
+
'int8': 'Int8'
|
|
43
|
+
},
|
|
44
|
+
'embedded_coder': {
|
|
45
|
+
'Simulink.IntEnumType': 'int32_T',
|
|
46
|
+
'int32': 'int32_T',
|
|
47
|
+
'uint16': 'uint16_T',
|
|
48
|
+
'int16': 'int16_T',
|
|
49
|
+
'uint8': 'uint8_T',
|
|
50
|
+
'int8': 'int8_T'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def __init__(self, build_prj_config, unit_configs):
|
|
55
|
+
"""Class Initialization.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
build_prj_config (BuildProjConfig): Instance holding information of where to find units configs to parse.
|
|
59
|
+
unit_configs (UnitConfigs): Unit definitions.
|
|
60
|
+
"""
|
|
61
|
+
super().__init__()
|
|
62
|
+
if not isinstance(build_prj_config, BuildProjConfig) or not isinstance(unit_configs, UnitConfigs):
|
|
63
|
+
err = (
|
|
64
|
+
'Input arguments should be an instance of:'
|
|
65
|
+
f'BuildProjConfig, not {type(build_prj_config)}'
|
|
66
|
+
f'AND/OR UnitConfigs, not {type(unit_configs)}'
|
|
67
|
+
)
|
|
68
|
+
raise TypeError(err)
|
|
69
|
+
|
|
70
|
+
start_time = time.time()
|
|
71
|
+
self.info(' Start parsing files with user defined data types')
|
|
72
|
+
self._build_prj_cfg = build_prj_config
|
|
73
|
+
self._unit_configs = unit_configs
|
|
74
|
+
self.enums_per_unit = {}
|
|
75
|
+
self.common_enums = self._read_enums_from_definitions()
|
|
76
|
+
self.structs_per_unit = {}
|
|
77
|
+
self._parse_all_user_defined_types()
|
|
78
|
+
self.common_header_files = []
|
|
79
|
+
# Must run last to be able to compare with TL/EC data types
|
|
80
|
+
self.all_enums = self._get_enumerations()
|
|
81
|
+
self._interface_data_types = self._parse_interface_data_types()
|
|
82
|
+
self.info(' Finished parsing files with user defined data types (in %4.2f s)', time.time() - start_time)
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def convert_interface_enum_to_simulink(interface_enum, underlying_data_type=None):
|
|
86
|
+
""" Given an interface enumeration, convert it to a simulink parsed version.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
interface_enum ([dict]): Interface enumeration definition.
|
|
90
|
+
underlying_data_type (str): Underlying data type, usually not available for interface enumerations.
|
|
91
|
+
Returns
|
|
92
|
+
simulink_enum (dict): Simulink enumeration definition.
|
|
93
|
+
"""
|
|
94
|
+
members = {}
|
|
95
|
+
default_value = None
|
|
96
|
+
# Assuming interface enumerations start at 0 counting upwards
|
|
97
|
+
for idx, enum_member in enumerate(interface_enum):
|
|
98
|
+
if 'default' in enum_member:
|
|
99
|
+
default_value = enum_member['in']
|
|
100
|
+
else:
|
|
101
|
+
members[enum_member['in']] = idx
|
|
102
|
+
simulink_enum = {
|
|
103
|
+
'underlying_data_type': underlying_data_type,
|
|
104
|
+
'members': members,
|
|
105
|
+
'default_value': default_value
|
|
106
|
+
}
|
|
107
|
+
return simulink_enum
|
|
108
|
+
|
|
109
|
+
def _parse_interface_data_types(self):
|
|
110
|
+
""" Get interface data types.
|
|
111
|
+
Also, runs validation tests.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
interface_data_types (dict): Specification interface data types.
|
|
115
|
+
"""
|
|
116
|
+
interface_data_types = {}
|
|
117
|
+
interface_data_types_path = Path(self._build_prj_cfg.get_root_dir(), 'conf.local', 'interface_data_types.yml')
|
|
118
|
+
|
|
119
|
+
if not interface_data_types_path.is_file():
|
|
120
|
+
self.warning('Cannot extract interface data types. File not found: %s', str(interface_data_types_path))
|
|
121
|
+
return interface_data_types
|
|
122
|
+
|
|
123
|
+
self.info(' Parsing file: %s', str(interface_data_types_path))
|
|
124
|
+
with interface_data_types_path.open(mode='r', encoding='utf-8') as fh:
|
|
125
|
+
yaml = YAML(typ='safe', pure=True)
|
|
126
|
+
interface_data_types_yaml = yaml.load(fh)
|
|
127
|
+
interface_data_types_tmp = interface_data_types_yaml['types']
|
|
128
|
+
# Add more as needed
|
|
129
|
+
if 'enums' in interface_data_types_tmp:
|
|
130
|
+
valid_interface_enumerations = self._validate_interface_enumerations(interface_data_types_tmp['enums'])
|
|
131
|
+
interface_data_types['enums'] = valid_interface_enumerations
|
|
132
|
+
return interface_data_types
|
|
133
|
+
|
|
134
|
+
def _parse_all_user_defined_types(self):
|
|
135
|
+
"""Parse all files containing user defined data types."""
|
|
136
|
+
src_dirs = self._build_prj_cfg.get_unit_src_dirs()
|
|
137
|
+
for unit, src_dir in src_dirs.items():
|
|
138
|
+
self._parse_unit_user_defined_types(unit, src_dir)
|
|
139
|
+
|
|
140
|
+
def _parse_unit_user_defined_types(self, unit, unit_src_dir):
|
|
141
|
+
"""Parse unit defined types for a given unit.
|
|
142
|
+
|
|
143
|
+
Files to parse are found using expected prefixes, see FILE_PREFIXES.
|
|
144
|
+
Data types to parse can be added as needed.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
unit (str): Current unit/model name.
|
|
148
|
+
unit_src_dir (str): Unit sourcecode directory.
|
|
149
|
+
"""
|
|
150
|
+
self.enums_per_unit[unit] = {}
|
|
151
|
+
self.structs_per_unit[unit] = {}
|
|
152
|
+
found_files = [Path(unit_src_dir, unit.split('__')[0] + '.h')]
|
|
153
|
+
for file_prefix in self.FILE_PREFIXES:
|
|
154
|
+
found_files.extend(Path(unit_src_dir).glob(file_prefix + '*.h'))
|
|
155
|
+
for found_file in found_files:
|
|
156
|
+
self.debug(' Parsing file: %s', str(found_file))
|
|
157
|
+
self._parse_target_link_enum(unit, found_file)
|
|
158
|
+
self._parse_target_link_struct(unit, found_file)
|
|
159
|
+
self._validate_project_enumerations()
|
|
160
|
+
self._validate_project_structs()
|
|
161
|
+
|
|
162
|
+
def _parse_target_link_struct(self, unit, header_file: Path):
|
|
163
|
+
"""Parse structs generated by TargetLink, given a header file.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
unit (str): Current unit/model name.
|
|
167
|
+
header_file (Path): Header file to parse.
|
|
168
|
+
"""
|
|
169
|
+
with header_file.open(mode='r', encoding='ISO-8859-1') as hf_fh:
|
|
170
|
+
header_file_content = hf_fh.read()
|
|
171
|
+
structs = re.findall(
|
|
172
|
+
r'^struct ([A-Za-z0-9_]+) {\s*\n'
|
|
173
|
+
r'((?:\s*\w+ \w+;\s*\n)*)'
|
|
174
|
+
r'};',
|
|
175
|
+
header_file_content,
|
|
176
|
+
flags=re.M
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
for struct_name, struct_content in structs:
|
|
180
|
+
struct_members = re.findall(r'\s*(\w+) (\w+);', struct_content, flags=re.M)
|
|
181
|
+
struct_dict = {
|
|
182
|
+
'members': {variable: data_type for data_type, variable in struct_members}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if struct_name in self.structs_per_unit[unit]:
|
|
186
|
+
if self.structs_per_unit[unit][struct_name]['members'] == struct_dict['members']:
|
|
187
|
+
self.info('Struct %s is multiply defined in %s, although they are consistent.', struct_name, unit)
|
|
188
|
+
else:
|
|
189
|
+
self.critical('Found inconsistent multiply defined struct: %s, units: %s.', struct_name, [unit])
|
|
190
|
+
else:
|
|
191
|
+
self.structs_per_unit[unit][struct_name] = struct_dict.copy()
|
|
192
|
+
|
|
193
|
+
def _validate_project_structs(self):
|
|
194
|
+
"""Checks for inconsistencies in struct definitions for all units in the project."""
|
|
195
|
+
structs = {}
|
|
196
|
+
for unit, struct_names in self.structs_per_unit.items():
|
|
197
|
+
for struct_name, struct_data in struct_names.items():
|
|
198
|
+
if struct_name in structs:
|
|
199
|
+
structs[struct_name]['units'].append(unit)
|
|
200
|
+
if not structs[struct_name]['members'] == struct_data['members']:
|
|
201
|
+
self.critical(
|
|
202
|
+
'Found inconsistent multiply defined struct: %s, units: %s.',
|
|
203
|
+
struct_name,
|
|
204
|
+
structs[struct_name]['units']
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
structs[struct_name] = struct_data.copy()
|
|
208
|
+
structs[struct_name]['units'] = [unit]
|
|
209
|
+
|
|
210
|
+
def _validate_interface_enumerations(self, interface_enumerations):
|
|
211
|
+
""" Given a specification for enumerations
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
interface_enumerations (dict): Interface enumerations.
|
|
215
|
+
Returns
|
|
216
|
+
valid_interface_enumerations (dict): Specification, valid interface enumerations.
|
|
217
|
+
"""
|
|
218
|
+
valid_interface_enumerations = {}
|
|
219
|
+
for enum_name, enum_data in interface_enumerations.items():
|
|
220
|
+
if enum_name in valid_interface_enumerations:
|
|
221
|
+
self.critical('%s is multiply defined in interface enumeration definitions.', enum_name)
|
|
222
|
+
elif enum_name not in self.all_enums:
|
|
223
|
+
self.critical('%s is not defined in the project', enum_name)
|
|
224
|
+
else:
|
|
225
|
+
converted = self.convert_interface_enum_to_simulink(
|
|
226
|
+
enum_data,
|
|
227
|
+
self.all_enums[enum_name]['underlying_data_type']
|
|
228
|
+
)
|
|
229
|
+
is_consistent = self._compare_enum_definitions(
|
|
230
|
+
self.all_enums[enum_name]['units'],
|
|
231
|
+
enum_name,
|
|
232
|
+
converted,
|
|
233
|
+
self.all_enums[enum_name]
|
|
234
|
+
)
|
|
235
|
+
if is_consistent:
|
|
236
|
+
valid_interface_enumerations[enum_name] = enum_data
|
|
237
|
+
return valid_interface_enumerations
|
|
238
|
+
|
|
239
|
+
def _validate_project_enumerations(self):
|
|
240
|
+
"""Checks for inconsistencies in enum definitions for all units in the project."""
|
|
241
|
+
enumerations = {}
|
|
242
|
+
for unit, enum_names in self.enums_per_unit.items():
|
|
243
|
+
for enum_name, enum_data in enum_names.items():
|
|
244
|
+
if enum_name in enumerations:
|
|
245
|
+
enumerations[enum_name]['units'].append(unit)
|
|
246
|
+
self._compare_enum_definitions(
|
|
247
|
+
enumerations[enum_name]['units'],
|
|
248
|
+
enum_name,
|
|
249
|
+
enumerations[enum_name],
|
|
250
|
+
enum_data
|
|
251
|
+
)
|
|
252
|
+
else:
|
|
253
|
+
enumerations[enum_name] = enum_data.copy()
|
|
254
|
+
enumerations[enum_name]['units'] = [unit]
|
|
255
|
+
|
|
256
|
+
def _compare_enum_definitions(self, units, enum_name, enum_one, enum_two):
|
|
257
|
+
"""Compare two enumeration definitions.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
units ([str]): List of units using given enumerations.
|
|
261
|
+
enum_name (str): Name of enumeration.
|
|
262
|
+
enum_one (dict): Enumeration one to compare.
|
|
263
|
+
enum_two (dict): Enumeration two to compare.
|
|
264
|
+
Returns:
|
|
265
|
+
is_consistent_enums (bool): True/False depending on enum consistency.
|
|
266
|
+
"""
|
|
267
|
+
is_consistent_enums = True
|
|
268
|
+
error_message = f'Found inconsistent multiply defined enumeration: {enum_name}, units: {units}.\n'
|
|
269
|
+
if enum_one['underlying_data_type'] != enum_two['underlying_data_type']:
|
|
270
|
+
is_consistent_enums = False
|
|
271
|
+
error_message += (
|
|
272
|
+
'underlying_data_type differs: '
|
|
273
|
+
f"{enum_one['underlying_data_type']} vs. {enum_two['underlying_data_type']}.\n"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
enum_one_members = list(enum_one['members'].items())
|
|
277
|
+
enum_two_members = list(enum_two['members'].items())
|
|
278
|
+
if len(enum_one_members) != len(enum_two_members):
|
|
279
|
+
is_consistent_enums = False
|
|
280
|
+
error_message += (
|
|
281
|
+
f'Number of enum members differs: {len(enum_one_members)} vs. {len(enum_two_members)}.\n'
|
|
282
|
+
)
|
|
283
|
+
else:
|
|
284
|
+
for idx, enum_one_member in enumerate(enum_one_members):
|
|
285
|
+
if enum_one_member[0] != enum_two_members[idx][0] or enum_one_member[1] != enum_two_members[idx][1]:
|
|
286
|
+
is_consistent_enums = False
|
|
287
|
+
error_message += (
|
|
288
|
+
f'Enum member definition differs: {enum_one_members[idx]} vs. {enum_two_members[idx]}.\n'
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
if enum_one['default_value'] != enum_two['default_value']:
|
|
292
|
+
is_consistent_enums = False
|
|
293
|
+
error_message += (
|
|
294
|
+
'default_value differs: '
|
|
295
|
+
f"{enum_one['default_value']} vs. {enum_two['default_value']}.\n"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
if not is_consistent_enums:
|
|
299
|
+
self.critical(error_message)
|
|
300
|
+
|
|
301
|
+
return is_consistent_enums
|
|
302
|
+
|
|
303
|
+
def _parse_target_link_enum(self, unit, header_file: Path):
|
|
304
|
+
"""Parse enumerations generated by TargetLink, given a header file.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
unit (str): Current unit/model name.
|
|
308
|
+
header_file (Path): Header file to parse.
|
|
309
|
+
"""
|
|
310
|
+
with header_file.open(mode='r', encoding='ISO-8859-1') as hf_fh:
|
|
311
|
+
header_file_content = hf_fh.read()
|
|
312
|
+
enums = re.findall(
|
|
313
|
+
r'^typedef enum ([A-Za-z0-9]+)_tag {\s*\n'
|
|
314
|
+
r'((?:\s*\w+ = [-+]?\d+,?\s*\n)*)'
|
|
315
|
+
r'} \1;',
|
|
316
|
+
header_file_content,
|
|
317
|
+
flags=re.M
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
for enum_name, enum_content in enums:
|
|
321
|
+
enum_members_tmp = re.findall(r'\s*(\w+) = ([-+]?\d+)', enum_content, flags=re.M)
|
|
322
|
+
enum_members = [(k, int(v)) for k, v in enum_members_tmp]
|
|
323
|
+
if enum_name in self.common_enums:
|
|
324
|
+
underlying_data_type = self.common_enums[enum_name]['underlying_data_type']
|
|
325
|
+
else:
|
|
326
|
+
self.warning('Calculating underlying data type for: %s', str(enum_name))
|
|
327
|
+
underlying_data_type = self._calculate_underlying_data_type(unit, enum_name, enum_members)
|
|
328
|
+
enum_dict = {
|
|
329
|
+
'underlying_data_type': underlying_data_type,
|
|
330
|
+
'members': {},
|
|
331
|
+
'default_value': self.get_default_enum_value(unit, enum_name)
|
|
332
|
+
}
|
|
333
|
+
for key, value in enum_members:
|
|
334
|
+
enum_dict['members'][key] = value
|
|
335
|
+
|
|
336
|
+
if enum_name in self.enums_per_unit[unit]:
|
|
337
|
+
is_consistent_enums = self._compare_enum_definitions(
|
|
338
|
+
[unit],
|
|
339
|
+
enum_name,
|
|
340
|
+
self.enums_per_unit[unit][enum_name],
|
|
341
|
+
enum_dict
|
|
342
|
+
)
|
|
343
|
+
if is_consistent_enums:
|
|
344
|
+
self.info(
|
|
345
|
+
'Enumeration %s is multiply defined in %s, although they are consistent.',
|
|
346
|
+
enum_name,
|
|
347
|
+
unit
|
|
348
|
+
)
|
|
349
|
+
else:
|
|
350
|
+
self.enums_per_unit[unit][enum_name] = enum_dict.copy()
|
|
351
|
+
|
|
352
|
+
def _calculate_underlying_data_type(self, unit, enum_name, enum_members):
|
|
353
|
+
"""Calculate best fitting data type given an enum definition.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
unit (str): Current unit/model name.
|
|
357
|
+
enum_name (str): Current enum name.
|
|
358
|
+
enum_members (list(tuple)): List of enum members, name value pairs.
|
|
359
|
+
Returns:
|
|
360
|
+
underlying_data_type (str): Best fitting data type.
|
|
361
|
+
"""
|
|
362
|
+
code_generator = self._unit_configs.get_unit_code_generator(unit)
|
|
363
|
+
enum_member_values = [member_value for member_name, member_value in enum_members]
|
|
364
|
+
min_value = min(enum_member_values)
|
|
365
|
+
max_value = max(enum_member_values)
|
|
366
|
+
|
|
367
|
+
# TODO Consider forcing signed like CS (or ARXML, from database?) does, seems to be int8 specifically
|
|
368
|
+
underlying_data_type = None
|
|
369
|
+
if min_value >= 0:
|
|
370
|
+
if max_value <= 255:
|
|
371
|
+
underlying_data_type = self.UNDERLYING_DATA_TYPES[code_generator]['u8']
|
|
372
|
+
elif max_value <= 65535:
|
|
373
|
+
underlying_data_type = self.UNDERLYING_DATA_TYPES[code_generator]['u16']
|
|
374
|
+
elif min_value >= -128:
|
|
375
|
+
if max_value <= 127:
|
|
376
|
+
underlying_data_type = self.UNDERLYING_DATA_TYPES[code_generator]['i8']
|
|
377
|
+
elif max_value <= 32767:
|
|
378
|
+
underlying_data_type = self.UNDERLYING_DATA_TYPES[code_generator]['i16']
|
|
379
|
+
elif min_value >= -32768:
|
|
380
|
+
if max_value <= 32767:
|
|
381
|
+
underlying_data_type = self.UNDERLYING_DATA_TYPES[code_generator]['i16']
|
|
382
|
+
|
|
383
|
+
if underlying_data_type is None:
|
|
384
|
+
self.critical(
|
|
385
|
+
'Unhandled enum size, name: %s, min: %s, max: %s. Valid types are uint8/16 and int8/16',
|
|
386
|
+
enum_name,
|
|
387
|
+
min_value,
|
|
388
|
+
max_value
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
return underlying_data_type
|
|
392
|
+
|
|
393
|
+
def _read_enums_from_definitions(self):
|
|
394
|
+
""" Reads all enums from .m files where they are defined. Requires projectInfo.enumDefDir in
|
|
395
|
+
ProjectCfg.json file.
|
|
396
|
+
Files defining simulink class enumerations are expected to follow a template.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
common_enums (dict): Dictionary containing all enums defined in the enum definition directory.
|
|
400
|
+
"""
|
|
401
|
+
common_enums = {}
|
|
402
|
+
if self._build_prj_cfg.get_enum_def_dir() is None:
|
|
403
|
+
self.warning(
|
|
404
|
+
'Cannot parse enumerations from .m files. Missing "enumDefDir" in project config.'
|
|
405
|
+
)
|
|
406
|
+
return {}
|
|
407
|
+
|
|
408
|
+
enum_files = Path(self._build_prj_cfg.get_enum_def_dir()).rglob('*.m')
|
|
409
|
+
for enum_file in enum_files:
|
|
410
|
+
with enum_file.open(mode='r', encoding='ISO-8859-1') as fh:
|
|
411
|
+
enum_file_content = fh.read()
|
|
412
|
+
enum_name = enum_file.stem
|
|
413
|
+
|
|
414
|
+
members_part = re.search(
|
|
415
|
+
r'\s+enumeration\s*\n'
|
|
416
|
+
r'(\s*%.*|\s*\w+\s*\(\d+\)\s*(%.*)?\n)+'
|
|
417
|
+
r'\s+end',
|
|
418
|
+
enum_file_content,
|
|
419
|
+
flags=re.MULTILINE
|
|
420
|
+
)
|
|
421
|
+
if not members_part:
|
|
422
|
+
self.warning('Cannot extract enumeration members from %s', str(enum_file))
|
|
423
|
+
continue
|
|
424
|
+
members = re.findall(r'(\w+)\s*\((\d+)\)', members_part.group(0))
|
|
425
|
+
member_dict = {enum_name + '_' + member[0]: int(member[1]) for member in members}
|
|
426
|
+
enum_members = [(member_name, member_value) for member_name, member_value in member_dict.items()]
|
|
427
|
+
|
|
428
|
+
get_default_value = re.search(
|
|
429
|
+
r'function ([A-Za-z0-9]+) = getDefaultValue\(\)[\s\n]*'
|
|
430
|
+
r'\1 = [A-Za-z0-9]+\.([A-Za-z0-9]+);[\s\n]*'
|
|
431
|
+
r'end',
|
|
432
|
+
enum_file_content,
|
|
433
|
+
flags=re.MULTILINE
|
|
434
|
+
)
|
|
435
|
+
if get_default_value is None:
|
|
436
|
+
self.warning('Cannot extract default enumeration values in: %s', str(enum_file))
|
|
437
|
+
continue
|
|
438
|
+
default_value = get_default_value.groups()[1]
|
|
439
|
+
default_member = f'{enum_name}_{default_value}'
|
|
440
|
+
|
|
441
|
+
calculate_underlying_data_type = re.search(
|
|
442
|
+
r'function ([A-Za-z0-9]+) = calculateUnderlyingDataType\(\)[\s\n]*'
|
|
443
|
+
r'\1 = (true|false);[\s\n]*'
|
|
444
|
+
r'end',
|
|
445
|
+
enum_file_content,
|
|
446
|
+
flags=re.MULTILINE
|
|
447
|
+
)
|
|
448
|
+
get_storage_type = re.search(
|
|
449
|
+
r'classdef[\s]+[A-Za-z0-9]+[\s]*<[\s]*([A-Za-z0-9\.]+)[\s\n]*',
|
|
450
|
+
enum_file_content,
|
|
451
|
+
flags=re.MULTILINE
|
|
452
|
+
)
|
|
453
|
+
if get_storage_type is None:
|
|
454
|
+
self.warning('Cannot extract storage type in: %s', str(enum_file))
|
|
455
|
+
if calculate_underlying_data_type is None or calculate_underlying_data_type.groups()[1] == 'false':
|
|
456
|
+
self.warning('Calculating underlying data type for: %s', str(enum_file))
|
|
457
|
+
underlying_data_type = self._calculate_underlying_data_type('target_link', enum_name, enum_members)
|
|
458
|
+
else:
|
|
459
|
+
underlying_data_type = self._calculate_underlying_data_type('target_link', enum_name, enum_members)
|
|
460
|
+
else:
|
|
461
|
+
underlying_data_type = self.SIMULINK_DATA_TYPES['target_link'][get_storage_type.groups()[0]]
|
|
462
|
+
|
|
463
|
+
common_enums[enum_name] = {
|
|
464
|
+
"underlying_data_type": underlying_data_type,
|
|
465
|
+
"default_value": default_member,
|
|
466
|
+
"members": member_dict
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return common_enums
|
|
470
|
+
|
|
471
|
+
def _get_enumerations(self):
|
|
472
|
+
"""Get all enumeration defined in the project, together with unit usage.
|
|
473
|
+
|
|
474
|
+
Information already provided in self._validate_project_enumerations during initialization.
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
enumerations (dict): Enumerations defined in the projects, including unit usage.
|
|
478
|
+
"""
|
|
479
|
+
enumerations = {}
|
|
480
|
+
for unit, enum_names in self.enums_per_unit.items():
|
|
481
|
+
for enum_name, enum_data in enum_names.items():
|
|
482
|
+
if enum_name in enumerations:
|
|
483
|
+
enumerations[enum_name]['units'].append(unit)
|
|
484
|
+
else:
|
|
485
|
+
enumerations[enum_name] = deepcopy(enum_data)
|
|
486
|
+
enumerations[enum_name]['units'] = [unit]
|
|
487
|
+
if self._build_prj_cfg.get_code_generation_config("includeAllEnums"):
|
|
488
|
+
for enum_name, enum_data in self.common_enums.items():
|
|
489
|
+
if enum_name not in enumerations:
|
|
490
|
+
self.warning(
|
|
491
|
+
"Enumeration %s is not used in any unit. Included since 'includeAllEnums' is set in config.",
|
|
492
|
+
enum_name
|
|
493
|
+
)
|
|
494
|
+
enumerations[enum_name] = deepcopy(enum_data)
|
|
495
|
+
enumerations[enum_name]['units'] = []
|
|
496
|
+
return enumerations
|
|
497
|
+
|
|
498
|
+
def get_default_enum_value(self, unit, enum_name):
|
|
499
|
+
"""Get default value of given enumeration name by searching in the unit configuration.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
unit (str): Current unit/model name.
|
|
503
|
+
enum_name (str): Name of an enumeration.
|
|
504
|
+
Returns:
|
|
505
|
+
default_value_str (str): Default value (string) for the given enumeration name.
|
|
506
|
+
None if value could not be extracted.
|
|
507
|
+
"""
|
|
508
|
+
# The config file is generated in one go, stop when first occurrence is found, cannot differ
|
|
509
|
+
per_unit_cfg = self._unit_configs.get_per_unit_cfg()
|
|
510
|
+
if unit in per_unit_cfg:
|
|
511
|
+
u_cfg = per_unit_cfg[unit]
|
|
512
|
+
else:
|
|
513
|
+
# Should not happen
|
|
514
|
+
self.warning(
|
|
515
|
+
'Cannot extract default enumeration value for %s in %s. Unit is missing in the unit configuration.',
|
|
516
|
+
enum_name,
|
|
517
|
+
unit
|
|
518
|
+
)
|
|
519
|
+
return None
|
|
520
|
+
|
|
521
|
+
for signal_type in ['inports', 'outports', 'local_vars', 'calib_consts']:
|
|
522
|
+
for signal_data in u_cfg[signal_type].values():
|
|
523
|
+
if enum_name == signal_data['type'] and 'default' in signal_data:
|
|
524
|
+
return f"{enum_name.upper()}_{signal_data['default'].upper()}"
|
|
525
|
+
|
|
526
|
+
self.warning(
|
|
527
|
+
'Cannot extract default enumeration value for %s in %s. '
|
|
528
|
+
'Either the enumeration or its default value is missing in the unit configuration file. ',
|
|
529
|
+
enum_name,
|
|
530
|
+
unit
|
|
531
|
+
)
|
|
532
|
+
common_default = self.common_enums.get(enum_name, {}).get('default_value', None)
|
|
533
|
+
if common_default is not None:
|
|
534
|
+
return common_default.upper()
|
|
535
|
+
return None
|
|
536
|
+
|
|
537
|
+
def get_enumerations(self):
|
|
538
|
+
"""Get all enumeration defined in the project, together with unit usage.
|
|
539
|
+
|
|
540
|
+
Returns:
|
|
541
|
+
self.all_enums (dict): Enumerations defined in the projects, including unit usage.
|
|
542
|
+
"""
|
|
543
|
+
return self.all_enums
|
|
544
|
+
|
|
545
|
+
def get_interface_data_types(self):
|
|
546
|
+
"""Returns all interface data types"""
|
|
547
|
+
return deepcopy(self._interface_data_types)
|
|
548
|
+
|
|
549
|
+
def get_structs(self):
|
|
550
|
+
"""Get all structs defined in the project, together with unit usage.
|
|
551
|
+
|
|
552
|
+
Information already provided in self._validate_project_structs during initialization.
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
structs (dict): Structs defined in the projects, including unit usage.
|
|
556
|
+
"""
|
|
557
|
+
structs = {}
|
|
558
|
+
for unit, struct_names in self.structs_per_unit.items():
|
|
559
|
+
for struct_name, struct_data in struct_names.items():
|
|
560
|
+
if struct_name in structs:
|
|
561
|
+
structs[struct_name]['units'].append(unit)
|
|
562
|
+
else:
|
|
563
|
+
structs[struct_name] = deepcopy(struct_data)
|
|
564
|
+
structs[struct_name]['units'] = [unit]
|
|
565
|
+
return structs
|
|
566
|
+
|
|
567
|
+
def _get_header_file_header(self, guard: str):
|
|
568
|
+
"""Get header for common header files.
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
guard (str): Guard for common header files.
|
|
572
|
+
Returns:
|
|
573
|
+
header (str): Header for common header files.
|
|
574
|
+
"""
|
|
575
|
+
return (
|
|
576
|
+
f'#ifndef {guard}\n'
|
|
577
|
+
f'#define {guard}\n'
|
|
578
|
+
f'{self._unit_configs.base_types_headers}'
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
def _generate_struct_header_file(self, file_path: Path):
|
|
582
|
+
""" Generates a header file declaring all TargetLink structs used in the project.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
file_path (Path): Path to file to generate.
|
|
586
|
+
"""
|
|
587
|
+
structs = self.get_structs()
|
|
588
|
+
guard = f"{file_path.stem.upper()}_H"
|
|
589
|
+
with file_path.open('w') as fh:
|
|
590
|
+
fh.write(self._get_header_file_header(guard))
|
|
591
|
+
fh.write('#include "VcEnumerations.h"\n') # struct members may be of enum type
|
|
592
|
+
fh.write('/* VCC Structs */\n')
|
|
593
|
+
for struct_name, struct_data in structs.items():
|
|
594
|
+
fh.write('\n')
|
|
595
|
+
fh.write(f'struct {struct_name} {{\n')
|
|
596
|
+
for variable, data_type in struct_data['members'].items():
|
|
597
|
+
fh.write(f" {data_type} {variable};\n")
|
|
598
|
+
fh.write('};\n')
|
|
599
|
+
fh.write(f'#endif /* {guard} */\n')
|
|
600
|
+
|
|
601
|
+
def _generate_enum_header_file(self, file_path: Path):
|
|
602
|
+
""" Generates a header file declaring all TargetLink enumerations used in the project.
|
|
603
|
+
|
|
604
|
+
Args:
|
|
605
|
+
file_path (Path): Path to file to generate.
|
|
606
|
+
"""
|
|
607
|
+
enumerations = self.get_enumerations()
|
|
608
|
+
rte_interface_enums = self._build_prj_cfg.get_code_generation_config("mapToRteEnums")
|
|
609
|
+
if not self._build_prj_cfg.get_enum_def_dir() and rte_interface_enums:
|
|
610
|
+
self.warning(
|
|
611
|
+
"No enum definition directory specified. Cannot generate all enums in decinition directory."
|
|
612
|
+
)
|
|
613
|
+
rte_interface_enums = False
|
|
614
|
+
guard = f"{file_path.stem.upper()}_H"
|
|
615
|
+
with file_path.open('w') as fh:
|
|
616
|
+
fh.write(self._get_header_file_header(guard))
|
|
617
|
+
if rte_interface_enums:
|
|
618
|
+
composition_name = self._build_prj_cfg.get_composition_config("softwareComponentName")
|
|
619
|
+
fh.write(f'#include "Rte_{ composition_name }_Type.h"\n')
|
|
620
|
+
fh.write('/* VCC Enumerations */\n')
|
|
621
|
+
for enum_name, enum_data in enumerations.items():
|
|
622
|
+
members = enum_data['members']
|
|
623
|
+
member_strs = ', \n'.join([f" {member.upper()} = {value}" for member, value in members.items()])
|
|
624
|
+
enum_definition = (
|
|
625
|
+
f'\n'
|
|
626
|
+
f'typedef enum {enum_name}_tag {{\n'
|
|
627
|
+
f'{member_strs} \n'
|
|
628
|
+
f'}} {enum_name};\n'
|
|
629
|
+
)
|
|
630
|
+
if rte_interface_enums:
|
|
631
|
+
enum = self.common_enums[enum_name]
|
|
632
|
+
one_member = next(iter(enum["members"]))
|
|
633
|
+
rte_translation = '\n'.join(
|
|
634
|
+
f"#define {member.upper()} {member}" for member in enum["members"].keys()
|
|
635
|
+
)
|
|
636
|
+
enum_definition = (
|
|
637
|
+
f'\n'
|
|
638
|
+
f'#ifndef {one_member}\n'
|
|
639
|
+
f'{enum_definition}\n'
|
|
640
|
+
f'#else\n'
|
|
641
|
+
f'{rte_translation}\n'
|
|
642
|
+
f'#endif /* {one_member} */\n'
|
|
643
|
+
)
|
|
644
|
+
fh.write(enum_definition)
|
|
645
|
+
fh.write(f'#endif /* {guard} */\n')
|
|
646
|
+
|
|
647
|
+
def generate_common_header_files(self):
|
|
648
|
+
""" Generates common header files.
|
|
649
|
+
|
|
650
|
+
These headers can be included in general files such as VcDummy_spm.h or VcExtVar.h.
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
common_header_files (list(Path)): List of names of generated files.
|
|
654
|
+
"""
|
|
655
|
+
src_dir = self._build_prj_cfg.get_src_code_dst_dir()
|
|
656
|
+
enum_file_path = Path(src_dir, 'VcEnumerations.h')
|
|
657
|
+
struct_file_path = Path(src_dir, 'VcStructs.h')
|
|
658
|
+
self._generate_enum_header_file(enum_file_path)
|
|
659
|
+
self._generate_struct_header_file(struct_file_path)
|
|
660
|
+
self.common_header_files.extend([enum_file_path.name, struct_file_path.name])
|