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.
Files changed (132) hide show
  1. powertrain_build/__init__.py +40 -40
  2. powertrain_build/__main__.py +6 -6
  3. powertrain_build/a2l.py +582 -582
  4. powertrain_build/a2l_merge.py +650 -650
  5. powertrain_build/a2l_templates.py +717 -717
  6. powertrain_build/build.py +985 -985
  7. powertrain_build/build_defs.py +309 -309
  8. powertrain_build/build_proj_config.py +690 -690
  9. powertrain_build/check_interface.py +575 -575
  10. powertrain_build/cli.py +141 -141
  11. powertrain_build/config.py +542 -542
  12. powertrain_build/core.py +395 -395
  13. powertrain_build/core_dummy.py +343 -343
  14. powertrain_build/create_conversion_table.py +73 -73
  15. powertrain_build/dids.py +916 -916
  16. powertrain_build/dummy.py +157 -157
  17. powertrain_build/dummy_spm.py +252 -252
  18. powertrain_build/environmentcheck.py +52 -52
  19. powertrain_build/ext_dbg.py +255 -255
  20. powertrain_build/ext_var.py +327 -327
  21. powertrain_build/feature_configs.py +301 -301
  22. powertrain_build/gen_allsysteminfo.py +227 -227
  23. powertrain_build/gen_label_split.py +449 -449
  24. powertrain_build/handcode_replacer.py +124 -124
  25. powertrain_build/html_report.py +133 -133
  26. powertrain_build/interface/__init__.py +4 -4
  27. powertrain_build/interface/application.py +511 -511
  28. powertrain_build/interface/base.py +500 -500
  29. powertrain_build/interface/csp_api.py +490 -490
  30. powertrain_build/interface/device_proxy.py +677 -677
  31. powertrain_build/interface/ems.py +67 -67
  32. powertrain_build/interface/export_global_vars.py +121 -121
  33. powertrain_build/interface/generate_adapters.py +132 -132
  34. powertrain_build/interface/generate_hi_interface.py +87 -87
  35. powertrain_build/interface/generate_service.py +69 -69
  36. powertrain_build/interface/generate_wrappers.py +147 -147
  37. powertrain_build/interface/generation_utils.py +142 -142
  38. powertrain_build/interface/hal.py +194 -194
  39. powertrain_build/interface/model_yaml_verification.py +348 -348
  40. powertrain_build/interface/service.py +296 -296
  41. powertrain_build/interface/simulink.py +249 -249
  42. powertrain_build/interface/update_call_sources.py +180 -180
  43. powertrain_build/interface/update_model_yaml.py +186 -186
  44. powertrain_build/interface/zone_controller.py +362 -362
  45. powertrain_build/lib/__init__.py +4 -4
  46. powertrain_build/lib/helper_functions.py +127 -127
  47. powertrain_build/lib/logger.py +55 -55
  48. powertrain_build/matlab_scripts/CodeGen/BuildAutomationPyBuild.m +78 -78
  49. powertrain_build/matlab_scripts/CodeGen/Generate_A2L.m +154 -154
  50. powertrain_build/matlab_scripts/CodeGen/generateTLUnit.m +239 -239
  51. powertrain_build/matlab_scripts/CodeGen/getAsilClassification.m +28 -28
  52. powertrain_build/matlab_scripts/CodeGen/modelConfiguredForTL.m +28 -28
  53. powertrain_build/matlab_scripts/CodeGen/moveDefOutports.m +88 -88
  54. powertrain_build/matlab_scripts/CodeGen/parseCalMeasData.m +410 -410
  55. powertrain_build/matlab_scripts/CodeGen/parseCoreIdentifiers.m +139 -139
  56. powertrain_build/matlab_scripts/CodeGen/parseDIDs.m +141 -141
  57. powertrain_build/matlab_scripts/CodeGen/parseInPorts.m +106 -106
  58. powertrain_build/matlab_scripts/CodeGen/parseIncludeConfigs.m +25 -25
  59. powertrain_build/matlab_scripts/CodeGen/parseModelInfo.m +38 -38
  60. powertrain_build/matlab_scripts/CodeGen/parseNVM.m +81 -81
  61. powertrain_build/matlab_scripts/CodeGen/parseOutPorts.m +120 -120
  62. powertrain_build/matlab_scripts/CodeGen/parsePreProcBlks.m +23 -23
  63. powertrain_build/matlab_scripts/CodeGen/struct2JSON.m +128 -128
  64. powertrain_build/matlab_scripts/CodeGen/updateCodeSwConfig.m +31 -31
  65. powertrain_build/matlab_scripts/Init_PyBuild.m +91 -91
  66. powertrain_build/matlab_scripts/__init__.py +2 -2
  67. powertrain_build/matlab_scripts/helperFunctions/Get_Full_Name.m +46 -46
  68. powertrain_build/matlab_scripts/helperFunctions/Get_SrcLines.m +12 -12
  69. powertrain_build/matlab_scripts/helperFunctions/Init_Models.m +78 -78
  70. powertrain_build/matlab_scripts/helperFunctions/Init_Projects.m +67 -67
  71. powertrain_build/matlab_scripts/helperFunctions/Read_Units.m +34 -34
  72. powertrain_build/matlab_scripts/helperFunctions/SetProjectTimeSamples.m +26 -26
  73. powertrain_build/matlab_scripts/helperFunctions/Strip_Suffix.m +16 -16
  74. powertrain_build/matlab_scripts/helperFunctions/followLink.m +118 -118
  75. powertrain_build/matlab_scripts/helperFunctions/getCodeSwitches.m +50 -50
  76. powertrain_build/matlab_scripts/helperFunctions/getConsumerBlocks.m +30 -30
  77. powertrain_build/matlab_scripts/helperFunctions/getDefBlock.m +39 -39
  78. powertrain_build/matlab_scripts/helperFunctions/getDefOutport.m +58 -58
  79. powertrain_build/matlab_scripts/helperFunctions/getDstBlocks.m +19 -19
  80. powertrain_build/matlab_scripts/helperFunctions/getDstLines.m +13 -13
  81. powertrain_build/matlab_scripts/helperFunctions/getInterfaceSignals.m +37 -37
  82. powertrain_build/matlab_scripts/helperFunctions/getName.m +37 -37
  83. powertrain_build/matlab_scripts/helperFunctions/getPath.m +6 -6
  84. powertrain_build/matlab_scripts/helperFunctions/getProperValue.m +21 -21
  85. powertrain_build/matlab_scripts/helperFunctions/getSrcBlocks.m +19 -19
  86. powertrain_build/matlab_scripts/helperFunctions/getSrcLines.m +13 -13
  87. powertrain_build/matlab_scripts/helperFunctions/loadLibraries.m +10 -10
  88. powertrain_build/matlab_scripts/helperFunctions/loadjson.m +6 -6
  89. powertrain_build/matlab_scripts/helperFunctions/modifyEnumStructField.m +21 -21
  90. powertrain_build/matlab_scripts/helperFunctions/removeConfigDuplicates.m +31 -31
  91. powertrain_build/matlab_scripts/helperFunctions/sortSystemByClass.m +26 -26
  92. powertrain_build/matlab_scripts/helperFunctions/tl_getfast.m +89 -89
  93. powertrain_build/matlab_scripts/helperFunctions/topLevelSystem.m +20 -20
  94. powertrain_build/matlab_scripts/helperFunctions/updateModels.m +131 -131
  95. powertrain_build/memory_section.py +224 -224
  96. powertrain_build/nvm_def.py +729 -729
  97. powertrain_build/problem_logger.py +86 -86
  98. powertrain_build/pt_matlab.py +430 -430
  99. powertrain_build/pt_win32.py +144 -144
  100. powertrain_build/replace_compu_tab_ref.py +105 -105
  101. powertrain_build/rte_dummy.py +254 -254
  102. powertrain_build/sched_funcs.py +209 -207
  103. powertrain_build/signal.py +7 -7
  104. powertrain_build/signal_if_html_rep.py +221 -221
  105. powertrain_build/signal_if_html_rep_all.py +302 -302
  106. powertrain_build/signal_incons_html_rep.py +180 -180
  107. powertrain_build/signal_incons_html_rep_all.py +366 -366
  108. powertrain_build/signal_incons_html_rep_base.py +168 -168
  109. powertrain_build/signal_inconsistency_check.py +641 -641
  110. powertrain_build/signal_interfaces.py +864 -864
  111. powertrain_build/templates/Index_SigCheck_All.html +22 -22
  112. powertrain_build/templates/Index_SigIf_All.html +19 -19
  113. powertrain_build/types.py +218 -218
  114. powertrain_build/unit_configs.py +419 -419
  115. powertrain_build/user_defined_types.py +660 -660
  116. powertrain_build/versioncheck.py +66 -66
  117. powertrain_build/wrapper.py +512 -512
  118. powertrain_build/xlrd_csv.py +87 -87
  119. powertrain_build/zone_controller/__init__.py +4 -4
  120. powertrain_build/zone_controller/calibration.py +176 -176
  121. powertrain_build/zone_controller/composition_yaml.py +880 -878
  122. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/METADATA +100 -100
  123. powertrain_build-1.13.3.dev3.dist-info/RECORD +130 -0
  124. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/WHEEL +1 -1
  125. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/LICENSE +202 -202
  126. powertrain_build-1.13.3.dev3.dist-info/pbr.json +1 -0
  127. powertrain_build-1.13.1.dist-info/RECORD +0 -130
  128. powertrain_build-1.13.1.dist-info/pbr.json +0 -1
  129. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/entry_points.txt +0 -0
  130. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/AUTHORS +0 -0
  131. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/NOTICE +0 -0
  132. {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])