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,641 +1,641 @@
1
- # Copyright 2024 Volvo Car Corporation
2
- # Licensed under Apache 2.0.
3
-
4
- """ script for running running signal consistency check on specific models. Git HEAD or local.
5
- """
6
-
7
- import argparse
8
- import csv
9
- import os
10
- import sys
11
- from collections import defaultdict
12
- from os.path import join
13
- from pathlib import Path
14
- from typing import List, Optional
15
-
16
- import git
17
-
18
- from powertrain_build.build_proj_config import BuildProjConfig
19
- from powertrain_build.feature_configs import FeatureConfigs
20
- from powertrain_build.lib import helper_functions, logger
21
- from powertrain_build.signal_incons_html_rep_all import SigConsHtmlReportAll
22
- from powertrain_build.signal_interfaces import CsvSignalInterfaces, YamlSignalInterfaces
23
- from powertrain_build.unit_configs import UnitConfigs
24
- from powertrain_build.user_defined_types import UserDefinedTypes
25
-
26
- LOGGER = logger.create_logger(__file__)
27
- EXIT_CODE_OK = 0
28
- EXIT_CODE_UNPRODUCED = 1
29
- EXIT_CODE_MISSING_CONSUMER = 2
30
- # Files define in <mdl>.Unconsumed.csv is consumed by mdl or/and interface.
31
- EXIT_CODE_INCORRECT_CSV = 4
32
- EXIT_CODE_NEVER_ACTIVE_SIGNALS = 8
33
- UNCONSUMED_SIGNAL_FILE_PATTERN = "*_Unconsumed_Sig.csv"
34
- MODEL_ROOT_DIR = "Models"
35
- REPO_ROOT = helper_functions.get_repo_root()
36
- MISSING_CONSUMER_CONSOLE_MESSAGE = (
37
- "\n===============Unused signals===============\n{signals}"
38
- )
39
- UNPRODUCED_SIGNALS_CONSOLE_MESSAGE = (
40
- "\n=============Unproduced signals=============\n{signals}"
41
- )
42
- UNCONSUMED_CSV_CONSOLE_MESSAGE = "\n=========Signals defined in unconsumed.csv-s that are consumed=========\n{signals}"
43
- NEVER_ACTIVE_SIGNALS_CONSOLE_MESSAGE = (
44
- "\n============Never active signals=============\n{signals}"
45
- )
46
- INDEX_FILE = "Reports/Index_SigCheck_All.html"
47
- TEMPLATE = """<!DOCTYPE html>
48
- <!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
49
- <!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
50
-
51
- <body class="wy-body-for-nav">
52
-
53
- <a class="icon icon-home"> Signal Consistency Report
54
- </a>
55
- <ul>
56
- <li class="toctree-l1">
57
- <a class="reference internal" href="SigCheckAll_intro.html">Introduction</a>
58
- </li>
59
- {project_rows}
60
- </ul>
61
- </body>
62
- </html>"""
63
-
64
- PARSER_HELP = "Run signal inconsistency check."
65
-
66
-
67
- def gen_sig_incons_index_file(project_list):
68
- """Generate Index_SigCheck_All.html."""
69
- # TODO Remove function and add as method in powertrain_build/signal_incons_html_rep_all.py
70
- rows = ""
71
- project_row = """<li class="toctree-l1"><a class="reference internal"
72
- href="SigCheckAll_{project}.html">SigCheckAll {project}</a></li>\n"""
73
- for prj in project_list:
74
- rows += project_row.format(project=prj)
75
- os.makedirs(os.path.dirname(INDEX_FILE), exist_ok=True)
76
- with open(INDEX_FILE, "w", encoding="utf-8") as f_h:
77
- f_h.write(TEMPLATE.format(project_rows=rows))
78
-
79
-
80
- def configure_parser(parser: argparse.ArgumentParser):
81
- """Parse the arguments sent to the script."""
82
- parser.add_argument(
83
- '-p',
84
- '--project-folders',
85
- nargs='+',
86
- default=['Projects'],
87
- help='Directories to look for project configurations.'
88
- )
89
- parser.add_argument(
90
- "-m",
91
- "--models",
92
- nargs="+",
93
- help="Arguments for running in local repo. "
94
- "Name of models to run test on Ex: VcDepExt VcAcCtrl. "
95
- "If not supplied, changed files in current commit will be used. "
96
- "Reports are stored in local repo",
97
- )
98
- parser.add_argument(
99
- "-r", "--report", help="Create report for all projects", action="store_true"
100
- )
101
- parser.set_defaults(func=run_signal_inconsistency_check)
102
-
103
-
104
- def get_project_configs(project_folders):
105
- """Wrapper for creating project configs for all projects.
106
-
107
- Args:
108
- project_folders (list): List of project folders to look for project configs in.
109
- Returns
110
- prj_cfgs (dict): Project configs.
111
- """
112
- project_config_files = []
113
- root_path = Path().resolve()
114
- for project_folder in project_folders:
115
- found_files = root_path.glob(f"{project_folder}/**/ProjectCfg.json")
116
- project_config_files.extend(list(map(lambda x: x.as_posix(), found_files)))
117
- prj_cfgs = {}
118
- for project_config_file in project_config_files:
119
- LOGGER.debug("Get project config for %s", project_config_file)
120
- project_config = BuildProjConfig(os.path.normpath(project_config_file))
121
- prj_cfgs.update({project_config.name: project_config})
122
- return prj_cfgs
123
-
124
-
125
- def check_inconsistency(signal_ifs, never_active_signals):
126
- """Check signal inconsistency.
127
- Args:
128
- signal_ifs (CsvSignalInterfaces): class holding signal interface information.
129
- never_active_signals (dict): Dict of projects mapped to units mapped to "NEVER_ACTIVE" signals.
130
- Returns:
131
- inconsistency result (dict)
132
- """
133
- signal_inconsistency_results = {}
134
- for key, signal_if in signal_ifs.items():
135
- partial_result = signal_if.check_config()
136
- signal_inconsistency_results.update({key: partial_result})
137
- signal_inconsistency_results[key].update(
138
- {"never_active_signals": never_active_signals[key]}
139
- )
140
- return signal_inconsistency_results
141
-
142
-
143
- def get_signal_interfaces(prj_cfgs, models):
144
- """Wrapper for creating signal interface dict for all configs.
145
- Args:
146
- Project config.
147
- Returns:
148
- Signal interface dict.
149
- """
150
- signal_ifs = {}
151
- per_unit_cfgs = {}
152
- for prj, prj_cfg in prj_cfgs.items():
153
- LOGGER.info("Parsing interface %s", prj)
154
-
155
- feature_cfg = FeatureConfigs(prj_cfg)
156
- unit_cfg = UnitConfigs(prj_cfg, feature_cfg)
157
- user_defined_types = UserDefinedTypes(prj_cfg, unit_cfg)
158
-
159
- if prj_cfg.has_yaml_interface:
160
- signal_if = YamlSignalInterfaces(
161
- prj_cfg, unit_cfg, feature_cfg, user_defined_types, model_names=models
162
- )
163
- else:
164
- signal_if = CsvSignalInterfaces(prj_cfg, unit_cfg, models)
165
-
166
- signal_ifs.update({prj: signal_if})
167
- per_unit_cfgs.update({prj: unit_cfg.get_per_unit_cfg()})
168
-
169
- return signal_ifs, per_unit_cfgs
170
-
171
-
172
- def get_never_active_signals(prj_cfgs, models):
173
- """Functions for getting all signals marked as "NEVER_ACTIVE" in all projects.
174
-
175
- Args:
176
- prj_cfgs (list(BuildProjConfig)): List of project configurations to look for never active signals in.
177
- models (list): List of Simulink models to check.
178
- Returns:
179
- never_active_signals (dict): Dict of projects mapped to units mapped to its "NEVER_ACTIVE" signals.
180
- """
181
- never_active_signals = {}
182
- for prj, prj_cfg in prj_cfgs.items():
183
- LOGGER.info("searching for never active signals in %s", prj)
184
- feature_cfg = FeatureConfigs(prj_cfg)
185
- unit_cfg = UnitConfigs(prj_cfg, feature_cfg)
186
- per_unit_cfg_total = unit_cfg.get_per_unit_cfg_total()
187
- never_active_signals[prj] = {}
188
- for unit, unit_data in per_unit_cfg_total.items():
189
- if unit not in models:
190
- continue
191
- inactive_inports = []
192
- inactive_outports = []
193
- for inport, inport_data in unit_data["inports"].items():
194
- if "NEVER_ACTIVE" in inport_data["configs"]:
195
- inactive_inports.append(inport)
196
- for outport, outport_data in unit_data["outports"].items():
197
- if "NEVER_ACTIVE" in outport_data["configs"]:
198
- inactive_outports.append(outport)
199
- if inactive_inports or inactive_outports:
200
- never_active_signals[prj].update(
201
- {unit: inactive_inports + inactive_outports}
202
- )
203
-
204
- return never_active_signals
205
-
206
-
207
- class SignalInconsistency:
208
- """Class for running signal consistency check on specific models. Git HEAD or local."""
209
-
210
- def __init__(self, args):
211
- """Models in local repo."""
212
- self.never_active_signals = {}
213
- self.signal_inconsistency_results = {}
214
- self.per_unit_cfgs = {}
215
- self.signal_ifs = {}
216
- self.repo = git.Repo()
217
- if args.models:
218
- self.models = args.models
219
- else:
220
- mdl_file_paths = self.get_changed_models()
221
- self.models = self._get_model_names(mdl_file_paths)
222
-
223
- @staticmethod
224
- def _get_model_names(mdl_paths):
225
- """Get Change Model names."""
226
- mdl_index = 1
227
- return [os.path.split(i)[mdl_index].replace(".mdl", "") for i in mdl_paths]
228
-
229
- def _create_reports(self):
230
- """Create reports for all projects."""
231
- sig_report = SigConsHtmlReportAll(self.signal_inconsistency_results)
232
- sig_report.generate_report_file_signal_check(
233
- join(REPO_ROOT, "Reports", "SigCheckAll")
234
- )
235
-
236
- def _sort_internal_inconsistency_by_model(self):
237
- """Sort inconsistency by model."""
238
- sig = self.signal_inconsistency_results
239
- no_producer_int = {}
240
- missing_consumer_int = {}
241
- never_active_int = {}
242
- for project, val in sig.items():
243
- int_dict = val["sigs"]["int"]
244
- for mdl, data in int_dict.items():
245
- if data.get("missing"):
246
- if mdl not in no_producer_int:
247
- no_producer_int[mdl] = self._to_tuple_list(
248
- project, data["missing"]
249
- )
250
- else:
251
- no_producer_int[mdl] += self._to_tuple_list(
252
- project, data["missing"]
253
- )
254
- if data.get("unused"):
255
- if mdl not in missing_consumer_int:
256
- missing_consumer_int[mdl] = self._to_tuple_list(
257
- project, data["unused"]
258
- )
259
- else:
260
- missing_consumer_int[mdl] += self._to_tuple_list(
261
- project, data["unused"]
262
- )
263
- for mdl, never_active_signals in val["never_active_signals"].items():
264
- never_active_int[mdl] = [
265
- (signal, {project}) for signal in never_active_signals
266
- ]
267
-
268
- return (
269
- self._merge_tuple_list(no_producer_int),
270
- self._merge_tuple_list(missing_consumer_int),
271
- self._merge_tuple_list(never_active_int),
272
- )
273
-
274
- def _analyse_inconsistency(self):
275
- """Collect signals.
276
-
277
- Args:
278
- no_producer_int (dict): Consumed signals but not produced.
279
- missing_consumer_int (dict): Produced signals but not consumed.
280
- """
281
- exit_code = EXIT_CODE_OK
282
- log_message = ""
283
- (
284
- no_producer_int,
285
- missing_consumer_int,
286
- never_active_int,
287
- ) = self._sort_internal_inconsistency_by_model()
288
- used_model_inports = self.aggregate_model_inports()
289
- used_external_outports = self.aggregate_supplier_inports()
290
- exit_code_unproduced, unproduced_message = self.check_unproduced_signals(
291
- no_producer_int
292
- )
293
- (
294
- exit_code_missing_consumer,
295
- missing_consumer_message,
296
- ) = self.check_missing_consumer_signals(missing_consumer_int)
297
- exit_code_used_inports, used_inports_message = self.check_unconsumed_files(
298
- used_model_inports, used_external_outports
299
- )
300
- exit_code_never_active, never_active_message = self.check_never_active_signals(
301
- never_active_int
302
- )
303
- log_message += unproduced_message
304
- log_message += missing_consumer_message
305
- log_message += used_inports_message
306
- log_message += never_active_message
307
- exit_code += exit_code_unproduced
308
- exit_code += exit_code_missing_consumer
309
- exit_code += exit_code_used_inports
310
- exit_code += exit_code_never_active
311
- return exit_code, log_message
312
-
313
- def get_changed_models(self):
314
- """Get changed models in current commit."""
315
- changed_files_tmp = self.repo.git.diff(
316
- "--diff-filter=d", "--name-only", "HEAD~1"
317
- )
318
- changed_files = changed_files_tmp.splitlines()
319
- changed_models = [
320
- m for m in changed_files if m.endswith(".mdl") or m.endswith(".slx")
321
- ]
322
- return changed_models
323
-
324
- def check_unproduced_signals(self, unproduced_signals):
325
- """Check if models expects signals that is not produced.
326
- Args:
327
- unproduced_signals (dict):
328
- mdl:{signals:{projects...}...}...
329
- Returns:
330
- exit_code (int):
331
- Number of unproduced signals in checked models.
332
- console message (string):
333
- Shows if models has unproduced signals.
334
- """
335
- error_message = ""
336
- exit_code = EXIT_CODE_OK
337
- models_with_unproduced_signals = {
338
- m for m in unproduced_signals.keys() if m in self.models
339
- }
340
- for mdl in models_with_unproduced_signals:
341
- error_message += self._format_console_output(mdl, unproduced_signals[mdl])
342
- if mdl in unproduced_signals and unproduced_signals[mdl]:
343
- exit_code = EXIT_CODE_UNPRODUCED
344
- return exit_code, UNPRODUCED_SIGNALS_CONSOLE_MESSAGE.format(
345
- signals=error_message
346
- )
347
-
348
- def check_missing_consumer_signals(self, missing_consumer_signals):
349
- """Check if models expect signals that is not produced.
350
- Args:
351
- missing_consumer_signals (dict):
352
- mdl:{signals:{projects...}...}...
353
- Returns:
354
- exit_code (int):
355
- Number of unproduced signals in checked models.
356
- console message (string):
357
- Shows if models has unproduced signals.
358
- """
359
- error_message = ""
360
- exit_code = EXIT_CODE_OK
361
- unconsumed_skip_list = self.fetch_signals_to_skip()
362
- models_producing_not_consumed_signals = {
363
- m for m in missing_consumer_signals.keys() if m in self.models
364
- }
365
- for mdl in models_producing_not_consumed_signals:
366
- sigs = missing_consumer_signals[mdl]
367
- for k in set(sigs.keys()):
368
- if mdl in unconsumed_skip_list and k in unconsumed_skip_list[mdl]:
369
- del sigs[k]
370
- error_message += self._format_console_output(
371
- mdl, missing_consumer_signals[mdl]
372
- )
373
- if mdl in missing_consumer_signals and missing_consumer_signals[mdl]:
374
- exit_code = EXIT_CODE_MISSING_CONSUMER
375
- return exit_code, MISSING_CONSUMER_CONSOLE_MESSAGE.format(signals=error_message)
376
-
377
- def check_unconsumed_files(self, inports, ifs_outports):
378
- """Check that signals define in unconsumed.csv files are not being consumed
379
- Args:
380
- inports (set): all models inports in all projects.
381
- ifs_outports (set): all outports, all projects and all interfaces.
382
- Returns:
383
- exit_code (int):
384
- console message (string): Console error message (if test fail)
385
- """
386
- error_message_int = ""
387
- error_message_ext = ""
388
- error_message = ""
389
- exit_code = EXIT_CODE_OK
390
- unconsumed_skip_list = self.fetch_signals_to_skip()
391
-
392
- for producing_mdl, signals in unconsumed_skip_list.items():
393
- signal_intersect_int = signals & inports
394
- signal_intersect_ext = signals & ifs_outports
395
- if signal_intersect_int:
396
- exit_code, error_message_int = self.get_intersect_exit_code(
397
- producing_mdl, signal_intersect_int
398
- )
399
- if signal_intersect_ext:
400
- exit_code, error_message_int = self.get_intersect_exit_code(
401
- producing_mdl, signal_intersect_ext, False
402
- )
403
- error_message += error_message_int
404
- error_message += error_message_ext
405
- return exit_code, UNCONSUMED_CSV_CONSOLE_MESSAGE.format(signals=error_message)
406
-
407
- def check_never_active_signals(self, never_active_signals):
408
- """Check if there are any never active signals.
409
- Produce corresponding error code and message.
410
-
411
- Args:
412
- never_active_signals (dict): Dict mapping models to never active signals in projects.
413
- Returns:
414
- exit_code (int):
415
- console message (string): Console error message (if test fail)
416
- """
417
- error_message = ""
418
- exit_code = EXIT_CODE_OK
419
- if never_active_signals:
420
- exit_code = EXIT_CODE_NEVER_ACTIVE_SIGNALS
421
- for mdl, signals in never_active_signals.items():
422
- error_message += self._format_console_output(mdl, signals)
423
-
424
- return exit_code, NEVER_ACTIVE_SIGNALS_CONSOLE_MESSAGE.format(
425
- signals=error_message
426
- )
427
-
428
- def get_intersect_exit_code(
429
- self, producing_mdl, signal_intersect, int_signals=True
430
- ):
431
- """Determine if Unconsumed.csv and used inport signals overlap per project.
432
- Return exit code and which consumer and project is cause of failure.
433
- Args:
434
- producing_mdl (String): Model name.
435
- signal_intersect (set): Unconsumed.csv signals and used inports that overlap.
436
- int_signals (bool):
437
- True: Set exit code for internally overlaping signals (Models)
438
- False: Set exit code for external overlaping signals (Supplier)
439
- Returns:
440
- exit_code (int): Fail or succes
441
- error_message (String): Consumers.
442
- """
443
- exit_code = EXIT_CODE_OK
444
- console_message = "{producer}\n {signal} is consumed by {consumer} in {prj}\n"
445
- error_message = ""
446
- for sig in signal_intersect:
447
- if int_signals:
448
- consumers = self.get_consumer_int(sig)
449
- else:
450
- consumers = self.get_consumer_ext(sig)
451
- for unit, prj in consumers:
452
- if self.mdl_is_producer_in_prj(producing_mdl, prj, sig):
453
- error_message += console_message.format(
454
- producer=producing_mdl, signal=sig, consumer=unit, prj=prj
455
- )
456
- exit_code = EXIT_CODE_INCORRECT_CSV
457
- return exit_code, error_message
458
-
459
- def mdl_is_producer_in_prj(self, mdl, prj, signal):
460
- """Check that defined signal in <mdl>unconsumed.csv is produced in project"""
461
- try:
462
- return signal in self.per_unit_cfgs[prj][mdl]["outports"].keys()
463
- except KeyError:
464
- return False
465
-
466
- def get_consumer_int(self, signal):
467
- """Find internal consumers"""
468
- signal_consumers = []
469
- for prj, unit_cfgs in self.per_unit_cfgs.items():
470
- for unit, unit_cfg in unit_cfgs.items():
471
- if signal in unit_cfg["inports"].keys():
472
- signal_consumers.append((unit, prj))
473
- return signal_consumers
474
-
475
- def get_consumer_ext(self, signal):
476
- """Find external consumers"""
477
- signal_consumers = []
478
- for prj, if_output in self.signal_ifs.items():
479
- for sig_type, signals in if_output.get_external_outputs(prj).items():
480
- if signal in signals.keys():
481
- signal_consumers.append((sig_type, prj))
482
- return signal_consumers
483
-
484
- @staticmethod
485
- def _format_console_output(mdl, signal_dict):
486
- out = ""
487
- for signal, projects in signal_dict.items():
488
- out += " " + str(signal) + str(projects) + "\n"
489
- return f"\n{mdl}\n{out} \n"
490
-
491
- @staticmethod
492
- def _to_tuple_list(project, signal_project_dict):
493
- """Create list of tuples.
494
-
495
- NOTE: signal_project_dict is no longer a signal to project mapping.
496
- It was when PyBuild handled multiple projects at once.
497
-
498
- Args:
499
- project (str): project name.
500
- signal_project_dict (dict): signal1: {}, signal2: {}...
501
- Returns:
502
- list: [(signal1, {project}), (signal2, {project})...]
503
- """
504
- return [(signal, {project}) for signal in signal_project_dict.keys()]
505
-
506
- @staticmethod
507
- def _merge_tuple_list(t_list):
508
- """Merged list of tuples.
509
- Args (list(tuple...)):
510
- [(A,1), (A,2), (C,1)]
511
-
512
- Returns (list(tuple...)):
513
- [(A,[1,2]), (C,1)]
514
- """
515
- model_confs = {}
516
- for mdl, sig_prj_pair in t_list.items():
517
- model_conf = defaultdict(list)
518
- for signal, prj in sig_prj_pair:
519
- model_conf[signal].append(prj)
520
- model_confs[mdl] = model_conf
521
- return model_confs
522
-
523
- def fetch_signals_to_skip(self, model_root_dir=MODEL_ROOT_DIR):
524
- """fetch Unconsumed signals from csv files.
525
- Returns:
526
- unconsumed_signals (dict):
527
- {model: {sig1, sig2...}, model2: {sig1, sig2...}}
528
- """
529
- unconsumed_sig = {mdl: set() for mdl in self.models}
530
- # "mdl_name" = "mdl folder name". '..\\..\\mdl_name\\pybuild_cfg\\mdl_Unconsumed_Sig.csv'
531
- model_dir_index = -3
532
- signal_col_index = 0
533
- for file_name in Path(model_root_dir).glob(
534
- "**/" + UNCONSUMED_SIGNAL_FILE_PATTERN
535
- ):
536
- mdl_name = str(file_name).split(os.sep)[model_dir_index]
537
- if mdl_name in self.models:
538
- with file_name.open() as csv_file:
539
- csv_reader = csv.reader(csv_file)
540
- next(csv_reader) # Skip header.
541
- for row in csv_reader:
542
- unconsumed_sig[mdl_name].add(row[signal_col_index])
543
- return unconsumed_sig
544
-
545
- @staticmethod
546
- def _print_console_log_info(exit_code):
547
- """Prints explanatory message to console.
548
- NOTE: The exit_codes_messages variable needs the messages to be in increasing order,
549
- according to the exit code constans above.
550
- """
551
- missing_producer = (
552
- "Model/Models is configured to consume signals that are not produced. "
553
- "Remove signal or fix/add producer."
554
- )
555
- missing_consumer = (
556
- "Model/Models creates signals that are not consumed. "
557
- "Remove or add to <mdl>_Unconsumed.csv."
558
- )
559
- incorrect_csv = (
560
- "Consumed signals is defined in <mdl>_Unconsumed.csv. "
561
- "Remove signals or fix/remove consumer."
562
- )
563
- never_active_signals = (
564
- "Never active signals will not appear in generated .c file. "
565
- "Remove corresponding part in Simulink model, signals probablty lead to terminators."
566
- )
567
- exit_codes_messages = [
568
- missing_producer,
569
- missing_consumer,
570
- incorrect_csv,
571
- never_active_signals,
572
- ]
573
- exit_code_bit_field = [
574
- bool(int(b)) for b in bin(exit_code)[2:]
575
- ] # [2:] gets rid of 0b part
576
- for idx, value in enumerate(exit_code_bit_field):
577
- if value:
578
- LOGGER.info(exit_codes_messages[idx])
579
-
580
- def aggregate_model_inports(self):
581
- """Aggregate inport signals from models"""
582
- used_inports = set()
583
- for _, unit_cfg in self.per_unit_cfgs.items():
584
- for _, inp in unit_cfg.items():
585
- used_inports = used_inports.union(set(inp["inports"].keys()))
586
- return used_inports
587
-
588
- def aggregate_supplier_inports(self):
589
- """Aggregate outports from signal interface (supplier inports)"""
590
- external_outports = set()
591
- for if_output in self.signal_ifs.values():
592
- for signals in if_output.get_external_outputs().values():
593
- external_outports = external_outports.union(set(signals.keys()))
594
- return external_outports
595
-
596
- def run(self, args):
597
- """Run signal inconsistency check."""
598
- exit_code = EXIT_CODE_OK
599
- if self.models:
600
- prj_cfs = get_project_configs(args.project_folders)
601
- self.signal_ifs, self.per_unit_cfgs = get_signal_interfaces(
602
- prj_cfs, self.models
603
- )
604
- self.never_active_signals = get_never_active_signals(prj_cfs, self.models)
605
- LOGGER.info("Start check signal inconsistencies for: %s", self.models)
606
- self.signal_inconsistency_results = check_inconsistency(
607
- self.signal_ifs, self.never_active_signals
608
- )
609
- exit_code, log_output = self._analyse_inconsistency()
610
- LOGGER.info("Finished check signal inconsistencies %s", log_output)
611
- self._print_console_log_info(exit_code)
612
- if args.report:
613
- LOGGER.info(
614
- "Start generating interface inconsistencies html-report for all projects"
615
- )
616
- gen_sig_incons_index_file(
617
- list(self.signal_inconsistency_results.keys())
618
- )
619
- self._create_reports()
620
- LOGGER.info("Finished - generating inconsistencies html-reports")
621
- else:
622
- LOGGER.info("No models in change")
623
- return exit_code
624
-
625
-
626
- def run_signal_inconsistency_check(args: argparse.Namespace) -> int:
627
- """Create Signal Inconsistency instance and run checks."""
628
- sig_in = SignalInconsistency(args)
629
- return sig_in.run(args)
630
-
631
-
632
- def main(argv: Optional[List[str]] = None) -> int:
633
- """Create Signal Inconsistency instance and run checks."""
634
- parser = argparse.ArgumentParser(description=PARSER_HELP)
635
- configure_parser(parser)
636
- args = parser.parse_args(argv)
637
- return args.func(args)
638
-
639
-
640
- if __name__ == "__main__":
641
- sys.exit(main(sys.argv[1:]))
1
+ # Copyright 2024 Volvo Car Corporation
2
+ # Licensed under Apache 2.0.
3
+
4
+ """ script for running running signal consistency check on specific models. Git HEAD or local.
5
+ """
6
+
7
+ import argparse
8
+ import csv
9
+ import os
10
+ import sys
11
+ from collections import defaultdict
12
+ from os.path import join
13
+ from pathlib import Path
14
+ from typing import List, Optional
15
+
16
+ import git
17
+
18
+ from powertrain_build.build_proj_config import BuildProjConfig
19
+ from powertrain_build.feature_configs import FeatureConfigs
20
+ from powertrain_build.lib import helper_functions, logger
21
+ from powertrain_build.signal_incons_html_rep_all import SigConsHtmlReportAll
22
+ from powertrain_build.signal_interfaces import CsvSignalInterfaces, YamlSignalInterfaces
23
+ from powertrain_build.unit_configs import UnitConfigs
24
+ from powertrain_build.user_defined_types import UserDefinedTypes
25
+
26
+ LOGGER = logger.create_logger(__file__)
27
+ EXIT_CODE_OK = 0
28
+ EXIT_CODE_UNPRODUCED = 1
29
+ EXIT_CODE_MISSING_CONSUMER = 2
30
+ # Files define in <mdl>.Unconsumed.csv is consumed by mdl or/and interface.
31
+ EXIT_CODE_INCORRECT_CSV = 4
32
+ EXIT_CODE_NEVER_ACTIVE_SIGNALS = 8
33
+ UNCONSUMED_SIGNAL_FILE_PATTERN = "*_Unconsumed_Sig.csv"
34
+ MODEL_ROOT_DIR = "Models"
35
+ REPO_ROOT = helper_functions.get_repo_root()
36
+ MISSING_CONSUMER_CONSOLE_MESSAGE = (
37
+ "\n===============Unused signals===============\n{signals}"
38
+ )
39
+ UNPRODUCED_SIGNALS_CONSOLE_MESSAGE = (
40
+ "\n=============Unproduced signals=============\n{signals}"
41
+ )
42
+ UNCONSUMED_CSV_CONSOLE_MESSAGE = "\n=========Signals defined in unconsumed.csv-s that are consumed=========\n{signals}"
43
+ NEVER_ACTIVE_SIGNALS_CONSOLE_MESSAGE = (
44
+ "\n============Never active signals=============\n{signals}"
45
+ )
46
+ INDEX_FILE = "Reports/Index_SigCheck_All.html"
47
+ TEMPLATE = """<!DOCTYPE html>
48
+ <!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
49
+ <!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
50
+
51
+ <body class="wy-body-for-nav">
52
+
53
+ <a class="icon icon-home"> Signal Consistency Report
54
+ </a>
55
+ <ul>
56
+ <li class="toctree-l1">
57
+ <a class="reference internal" href="SigCheckAll_intro.html">Introduction</a>
58
+ </li>
59
+ {project_rows}
60
+ </ul>
61
+ </body>
62
+ </html>"""
63
+
64
+ PARSER_HELP = "Run signal inconsistency check."
65
+
66
+
67
+ def gen_sig_incons_index_file(project_list):
68
+ """Generate Index_SigCheck_All.html."""
69
+ # TODO Remove function and add as method in powertrain_build/signal_incons_html_rep_all.py
70
+ rows = ""
71
+ project_row = """<li class="toctree-l1"><a class="reference internal"
72
+ href="SigCheckAll_{project}.html">SigCheckAll {project}</a></li>\n"""
73
+ for prj in project_list:
74
+ rows += project_row.format(project=prj)
75
+ os.makedirs(os.path.dirname(INDEX_FILE), exist_ok=True)
76
+ with open(INDEX_FILE, "w", encoding="utf-8") as f_h:
77
+ f_h.write(TEMPLATE.format(project_rows=rows))
78
+
79
+
80
+ def configure_parser(parser: argparse.ArgumentParser):
81
+ """Parse the arguments sent to the script."""
82
+ parser.add_argument(
83
+ '-p',
84
+ '--project-folders',
85
+ nargs='+',
86
+ default=['Projects'],
87
+ help='Directories to look for project configurations.'
88
+ )
89
+ parser.add_argument(
90
+ "-m",
91
+ "--models",
92
+ nargs="+",
93
+ help="Arguments for running in local repo. "
94
+ "Name of models to run test on Ex: VcDepExt VcAcCtrl. "
95
+ "If not supplied, changed files in current commit will be used. "
96
+ "Reports are stored in local repo",
97
+ )
98
+ parser.add_argument(
99
+ "-r", "--report", help="Create report for all projects", action="store_true"
100
+ )
101
+ parser.set_defaults(func=run_signal_inconsistency_check)
102
+
103
+
104
+ def get_project_configs(project_folders):
105
+ """Wrapper for creating project configs for all projects.
106
+
107
+ Args:
108
+ project_folders (list): List of project folders to look for project configs in.
109
+ Returns
110
+ prj_cfgs (dict): Project configs.
111
+ """
112
+ project_config_files = []
113
+ root_path = Path().resolve()
114
+ for project_folder in project_folders:
115
+ found_files = root_path.glob(f"{project_folder}/**/ProjectCfg.json")
116
+ project_config_files.extend(list(map(lambda x: x.as_posix(), found_files)))
117
+ prj_cfgs = {}
118
+ for project_config_file in project_config_files:
119
+ LOGGER.debug("Get project config for %s", project_config_file)
120
+ project_config = BuildProjConfig(os.path.normpath(project_config_file))
121
+ prj_cfgs.update({project_config.name: project_config})
122
+ return prj_cfgs
123
+
124
+
125
+ def check_inconsistency(signal_ifs, never_active_signals):
126
+ """Check signal inconsistency.
127
+ Args:
128
+ signal_ifs (CsvSignalInterfaces): class holding signal interface information.
129
+ never_active_signals (dict): Dict of projects mapped to units mapped to "NEVER_ACTIVE" signals.
130
+ Returns:
131
+ inconsistency result (dict)
132
+ """
133
+ signal_inconsistency_results = {}
134
+ for key, signal_if in signal_ifs.items():
135
+ partial_result = signal_if.check_config()
136
+ signal_inconsistency_results.update({key: partial_result})
137
+ signal_inconsistency_results[key].update(
138
+ {"never_active_signals": never_active_signals[key]}
139
+ )
140
+ return signal_inconsistency_results
141
+
142
+
143
+ def get_signal_interfaces(prj_cfgs, models):
144
+ """Wrapper for creating signal interface dict for all configs.
145
+ Args:
146
+ Project config.
147
+ Returns:
148
+ Signal interface dict.
149
+ """
150
+ signal_ifs = {}
151
+ per_unit_cfgs = {}
152
+ for prj, prj_cfg in prj_cfgs.items():
153
+ LOGGER.info("Parsing interface %s", prj)
154
+
155
+ feature_cfg = FeatureConfigs(prj_cfg)
156
+ unit_cfg = UnitConfigs(prj_cfg, feature_cfg)
157
+ user_defined_types = UserDefinedTypes(prj_cfg, unit_cfg)
158
+
159
+ if prj_cfg.has_yaml_interface:
160
+ signal_if = YamlSignalInterfaces(
161
+ prj_cfg, unit_cfg, feature_cfg, user_defined_types, model_names=models
162
+ )
163
+ else:
164
+ signal_if = CsvSignalInterfaces(prj_cfg, unit_cfg, models)
165
+
166
+ signal_ifs.update({prj: signal_if})
167
+ per_unit_cfgs.update({prj: unit_cfg.get_per_unit_cfg()})
168
+
169
+ return signal_ifs, per_unit_cfgs
170
+
171
+
172
+ def get_never_active_signals(prj_cfgs, models):
173
+ """Functions for getting all signals marked as "NEVER_ACTIVE" in all projects.
174
+
175
+ Args:
176
+ prj_cfgs (list(BuildProjConfig)): List of project configurations to look for never active signals in.
177
+ models (list): List of Simulink models to check.
178
+ Returns:
179
+ never_active_signals (dict): Dict of projects mapped to units mapped to its "NEVER_ACTIVE" signals.
180
+ """
181
+ never_active_signals = {}
182
+ for prj, prj_cfg in prj_cfgs.items():
183
+ LOGGER.info("searching for never active signals in %s", prj)
184
+ feature_cfg = FeatureConfigs(prj_cfg)
185
+ unit_cfg = UnitConfigs(prj_cfg, feature_cfg)
186
+ per_unit_cfg_total = unit_cfg.get_per_unit_cfg_total()
187
+ never_active_signals[prj] = {}
188
+ for unit, unit_data in per_unit_cfg_total.items():
189
+ if unit not in models:
190
+ continue
191
+ inactive_inports = []
192
+ inactive_outports = []
193
+ for inport, inport_data in unit_data["inports"].items():
194
+ if "NEVER_ACTIVE" in inport_data["configs"]:
195
+ inactive_inports.append(inport)
196
+ for outport, outport_data in unit_data["outports"].items():
197
+ if "NEVER_ACTIVE" in outport_data["configs"]:
198
+ inactive_outports.append(outport)
199
+ if inactive_inports or inactive_outports:
200
+ never_active_signals[prj].update(
201
+ {unit: inactive_inports + inactive_outports}
202
+ )
203
+
204
+ return never_active_signals
205
+
206
+
207
+ class SignalInconsistency:
208
+ """Class for running signal consistency check on specific models. Git HEAD or local."""
209
+
210
+ def __init__(self, args):
211
+ """Models in local repo."""
212
+ self.never_active_signals = {}
213
+ self.signal_inconsistency_results = {}
214
+ self.per_unit_cfgs = {}
215
+ self.signal_ifs = {}
216
+ self.repo = git.Repo()
217
+ if args.models:
218
+ self.models = args.models
219
+ else:
220
+ mdl_file_paths = self.get_changed_models()
221
+ self.models = self._get_model_names(mdl_file_paths)
222
+
223
+ @staticmethod
224
+ def _get_model_names(mdl_paths):
225
+ """Get Change Model names."""
226
+ mdl_index = 1
227
+ return [os.path.split(i)[mdl_index].replace(".mdl", "") for i in mdl_paths]
228
+
229
+ def _create_reports(self):
230
+ """Create reports for all projects."""
231
+ sig_report = SigConsHtmlReportAll(self.signal_inconsistency_results)
232
+ sig_report.generate_report_file_signal_check(
233
+ join(REPO_ROOT, "Reports", "SigCheckAll")
234
+ )
235
+
236
+ def _sort_internal_inconsistency_by_model(self):
237
+ """Sort inconsistency by model."""
238
+ sig = self.signal_inconsistency_results
239
+ no_producer_int = {}
240
+ missing_consumer_int = {}
241
+ never_active_int = {}
242
+ for project, val in sig.items():
243
+ int_dict = val["sigs"]["int"]
244
+ for mdl, data in int_dict.items():
245
+ if data.get("missing"):
246
+ if mdl not in no_producer_int:
247
+ no_producer_int[mdl] = self._to_tuple_list(
248
+ project, data["missing"]
249
+ )
250
+ else:
251
+ no_producer_int[mdl] += self._to_tuple_list(
252
+ project, data["missing"]
253
+ )
254
+ if data.get("unused"):
255
+ if mdl not in missing_consumer_int:
256
+ missing_consumer_int[mdl] = self._to_tuple_list(
257
+ project, data["unused"]
258
+ )
259
+ else:
260
+ missing_consumer_int[mdl] += self._to_tuple_list(
261
+ project, data["unused"]
262
+ )
263
+ for mdl, never_active_signals in val["never_active_signals"].items():
264
+ never_active_int[mdl] = [
265
+ (signal, {project}) for signal in never_active_signals
266
+ ]
267
+
268
+ return (
269
+ self._merge_tuple_list(no_producer_int),
270
+ self._merge_tuple_list(missing_consumer_int),
271
+ self._merge_tuple_list(never_active_int),
272
+ )
273
+
274
+ def _analyse_inconsistency(self):
275
+ """Collect signals.
276
+
277
+ Args:
278
+ no_producer_int (dict): Consumed signals but not produced.
279
+ missing_consumer_int (dict): Produced signals but not consumed.
280
+ """
281
+ exit_code = EXIT_CODE_OK
282
+ log_message = ""
283
+ (
284
+ no_producer_int,
285
+ missing_consumer_int,
286
+ never_active_int,
287
+ ) = self._sort_internal_inconsistency_by_model()
288
+ used_model_inports = self.aggregate_model_inports()
289
+ used_external_outports = self.aggregate_supplier_inports()
290
+ exit_code_unproduced, unproduced_message = self.check_unproduced_signals(
291
+ no_producer_int
292
+ )
293
+ (
294
+ exit_code_missing_consumer,
295
+ missing_consumer_message,
296
+ ) = self.check_missing_consumer_signals(missing_consumer_int)
297
+ exit_code_used_inports, used_inports_message = self.check_unconsumed_files(
298
+ used_model_inports, used_external_outports
299
+ )
300
+ exit_code_never_active, never_active_message = self.check_never_active_signals(
301
+ never_active_int
302
+ )
303
+ log_message += unproduced_message
304
+ log_message += missing_consumer_message
305
+ log_message += used_inports_message
306
+ log_message += never_active_message
307
+ exit_code += exit_code_unproduced
308
+ exit_code += exit_code_missing_consumer
309
+ exit_code += exit_code_used_inports
310
+ exit_code += exit_code_never_active
311
+ return exit_code, log_message
312
+
313
+ def get_changed_models(self):
314
+ """Get changed models in current commit."""
315
+ changed_files_tmp = self.repo.git.diff(
316
+ "--diff-filter=d", "--name-only", "HEAD~1"
317
+ )
318
+ changed_files = changed_files_tmp.splitlines()
319
+ changed_models = [
320
+ m for m in changed_files if m.endswith(".mdl") or m.endswith(".slx")
321
+ ]
322
+ return changed_models
323
+
324
+ def check_unproduced_signals(self, unproduced_signals):
325
+ """Check if models expects signals that is not produced.
326
+ Args:
327
+ unproduced_signals (dict):
328
+ mdl:{signals:{projects...}...}...
329
+ Returns:
330
+ exit_code (int):
331
+ Number of unproduced signals in checked models.
332
+ console message (string):
333
+ Shows if models has unproduced signals.
334
+ """
335
+ error_message = ""
336
+ exit_code = EXIT_CODE_OK
337
+ models_with_unproduced_signals = {
338
+ m for m in unproduced_signals.keys() if m in self.models
339
+ }
340
+ for mdl in models_with_unproduced_signals:
341
+ error_message += self._format_console_output(mdl, unproduced_signals[mdl])
342
+ if mdl in unproduced_signals and unproduced_signals[mdl]:
343
+ exit_code = EXIT_CODE_UNPRODUCED
344
+ return exit_code, UNPRODUCED_SIGNALS_CONSOLE_MESSAGE.format(
345
+ signals=error_message
346
+ )
347
+
348
+ def check_missing_consumer_signals(self, missing_consumer_signals):
349
+ """Check if models expect signals that is not produced.
350
+ Args:
351
+ missing_consumer_signals (dict):
352
+ mdl:{signals:{projects...}...}...
353
+ Returns:
354
+ exit_code (int):
355
+ Number of unproduced signals in checked models.
356
+ console message (string):
357
+ Shows if models has unproduced signals.
358
+ """
359
+ error_message = ""
360
+ exit_code = EXIT_CODE_OK
361
+ unconsumed_skip_list = self.fetch_signals_to_skip()
362
+ models_producing_not_consumed_signals = {
363
+ m for m in missing_consumer_signals.keys() if m in self.models
364
+ }
365
+ for mdl in models_producing_not_consumed_signals:
366
+ sigs = missing_consumer_signals[mdl]
367
+ for k in set(sigs.keys()):
368
+ if mdl in unconsumed_skip_list and k in unconsumed_skip_list[mdl]:
369
+ del sigs[k]
370
+ error_message += self._format_console_output(
371
+ mdl, missing_consumer_signals[mdl]
372
+ )
373
+ if mdl in missing_consumer_signals and missing_consumer_signals[mdl]:
374
+ exit_code = EXIT_CODE_MISSING_CONSUMER
375
+ return exit_code, MISSING_CONSUMER_CONSOLE_MESSAGE.format(signals=error_message)
376
+
377
+ def check_unconsumed_files(self, inports, ifs_outports):
378
+ """Check that signals define in unconsumed.csv files are not being consumed
379
+ Args:
380
+ inports (set): all models inports in all projects.
381
+ ifs_outports (set): all outports, all projects and all interfaces.
382
+ Returns:
383
+ exit_code (int):
384
+ console message (string): Console error message (if test fail)
385
+ """
386
+ error_message_int = ""
387
+ error_message_ext = ""
388
+ error_message = ""
389
+ exit_code = EXIT_CODE_OK
390
+ unconsumed_skip_list = self.fetch_signals_to_skip()
391
+
392
+ for producing_mdl, signals in unconsumed_skip_list.items():
393
+ signal_intersect_int = signals & inports
394
+ signal_intersect_ext = signals & ifs_outports
395
+ if signal_intersect_int:
396
+ exit_code, error_message_int = self.get_intersect_exit_code(
397
+ producing_mdl, signal_intersect_int
398
+ )
399
+ if signal_intersect_ext:
400
+ exit_code, error_message_int = self.get_intersect_exit_code(
401
+ producing_mdl, signal_intersect_ext, False
402
+ )
403
+ error_message += error_message_int
404
+ error_message += error_message_ext
405
+ return exit_code, UNCONSUMED_CSV_CONSOLE_MESSAGE.format(signals=error_message)
406
+
407
+ def check_never_active_signals(self, never_active_signals):
408
+ """Check if there are any never active signals.
409
+ Produce corresponding error code and message.
410
+
411
+ Args:
412
+ never_active_signals (dict): Dict mapping models to never active signals in projects.
413
+ Returns:
414
+ exit_code (int):
415
+ console message (string): Console error message (if test fail)
416
+ """
417
+ error_message = ""
418
+ exit_code = EXIT_CODE_OK
419
+ if never_active_signals:
420
+ exit_code = EXIT_CODE_NEVER_ACTIVE_SIGNALS
421
+ for mdl, signals in never_active_signals.items():
422
+ error_message += self._format_console_output(mdl, signals)
423
+
424
+ return exit_code, NEVER_ACTIVE_SIGNALS_CONSOLE_MESSAGE.format(
425
+ signals=error_message
426
+ )
427
+
428
+ def get_intersect_exit_code(
429
+ self, producing_mdl, signal_intersect, int_signals=True
430
+ ):
431
+ """Determine if Unconsumed.csv and used inport signals overlap per project.
432
+ Return exit code and which consumer and project is cause of failure.
433
+ Args:
434
+ producing_mdl (String): Model name.
435
+ signal_intersect (set): Unconsumed.csv signals and used inports that overlap.
436
+ int_signals (bool):
437
+ True: Set exit code for internally overlaping signals (Models)
438
+ False: Set exit code for external overlaping signals (Supplier)
439
+ Returns:
440
+ exit_code (int): Fail or succes
441
+ error_message (String): Consumers.
442
+ """
443
+ exit_code = EXIT_CODE_OK
444
+ console_message = "{producer}\n {signal} is consumed by {consumer} in {prj}\n"
445
+ error_message = ""
446
+ for sig in signal_intersect:
447
+ if int_signals:
448
+ consumers = self.get_consumer_int(sig)
449
+ else:
450
+ consumers = self.get_consumer_ext(sig)
451
+ for unit, prj in consumers:
452
+ if self.mdl_is_producer_in_prj(producing_mdl, prj, sig):
453
+ error_message += console_message.format(
454
+ producer=producing_mdl, signal=sig, consumer=unit, prj=prj
455
+ )
456
+ exit_code = EXIT_CODE_INCORRECT_CSV
457
+ return exit_code, error_message
458
+
459
+ def mdl_is_producer_in_prj(self, mdl, prj, signal):
460
+ """Check that defined signal in <mdl>unconsumed.csv is produced in project"""
461
+ try:
462
+ return signal in self.per_unit_cfgs[prj][mdl]["outports"].keys()
463
+ except KeyError:
464
+ return False
465
+
466
+ def get_consumer_int(self, signal):
467
+ """Find internal consumers"""
468
+ signal_consumers = []
469
+ for prj, unit_cfgs in self.per_unit_cfgs.items():
470
+ for unit, unit_cfg in unit_cfgs.items():
471
+ if signal in unit_cfg["inports"].keys():
472
+ signal_consumers.append((unit, prj))
473
+ return signal_consumers
474
+
475
+ def get_consumer_ext(self, signal):
476
+ """Find external consumers"""
477
+ signal_consumers = []
478
+ for prj, if_output in self.signal_ifs.items():
479
+ for sig_type, signals in if_output.get_external_outputs(prj).items():
480
+ if signal in signals.keys():
481
+ signal_consumers.append((sig_type, prj))
482
+ return signal_consumers
483
+
484
+ @staticmethod
485
+ def _format_console_output(mdl, signal_dict):
486
+ out = ""
487
+ for signal, projects in signal_dict.items():
488
+ out += " " + str(signal) + str(projects) + "\n"
489
+ return f"\n{mdl}\n{out} \n"
490
+
491
+ @staticmethod
492
+ def _to_tuple_list(project, signal_project_dict):
493
+ """Create list of tuples.
494
+
495
+ NOTE: signal_project_dict is no longer a signal to project mapping.
496
+ It was when PyBuild handled multiple projects at once.
497
+
498
+ Args:
499
+ project (str): project name.
500
+ signal_project_dict (dict): signal1: {}, signal2: {}...
501
+ Returns:
502
+ list: [(signal1, {project}), (signal2, {project})...]
503
+ """
504
+ return [(signal, {project}) for signal in signal_project_dict.keys()]
505
+
506
+ @staticmethod
507
+ def _merge_tuple_list(t_list):
508
+ """Merged list of tuples.
509
+ Args (list(tuple...)):
510
+ [(A,1), (A,2), (C,1)]
511
+
512
+ Returns (list(tuple...)):
513
+ [(A,[1,2]), (C,1)]
514
+ """
515
+ model_confs = {}
516
+ for mdl, sig_prj_pair in t_list.items():
517
+ model_conf = defaultdict(list)
518
+ for signal, prj in sig_prj_pair:
519
+ model_conf[signal].append(prj)
520
+ model_confs[mdl] = model_conf
521
+ return model_confs
522
+
523
+ def fetch_signals_to_skip(self, model_root_dir=MODEL_ROOT_DIR):
524
+ """fetch Unconsumed signals from csv files.
525
+ Returns:
526
+ unconsumed_signals (dict):
527
+ {model: {sig1, sig2...}, model2: {sig1, sig2...}}
528
+ """
529
+ unconsumed_sig = {mdl: set() for mdl in self.models}
530
+ # "mdl_name" = "mdl folder name". '..\\..\\mdl_name\\pybuild_cfg\\mdl_Unconsumed_Sig.csv'
531
+ model_dir_index = -3
532
+ signal_col_index = 0
533
+ for file_name in Path(model_root_dir).glob(
534
+ "**/" + UNCONSUMED_SIGNAL_FILE_PATTERN
535
+ ):
536
+ mdl_name = str(file_name).split(os.sep)[model_dir_index]
537
+ if mdl_name in self.models:
538
+ with file_name.open() as csv_file:
539
+ csv_reader = csv.reader(csv_file)
540
+ next(csv_reader) # Skip header.
541
+ for row in csv_reader:
542
+ unconsumed_sig[mdl_name].add(row[signal_col_index])
543
+ return unconsumed_sig
544
+
545
+ @staticmethod
546
+ def _print_console_log_info(exit_code):
547
+ """Prints explanatory message to console.
548
+ NOTE: The exit_codes_messages variable needs the messages to be in increasing order,
549
+ according to the exit code constans above.
550
+ """
551
+ missing_producer = (
552
+ "Model/Models is configured to consume signals that are not produced. "
553
+ "Remove signal or fix/add producer."
554
+ )
555
+ missing_consumer = (
556
+ "Model/Models creates signals that are not consumed. "
557
+ "Remove or add to <mdl>_Unconsumed.csv."
558
+ )
559
+ incorrect_csv = (
560
+ "Consumed signals is defined in <mdl>_Unconsumed.csv. "
561
+ "Remove signals or fix/remove consumer."
562
+ )
563
+ never_active_signals = (
564
+ "Never active signals will not appear in generated .c file. "
565
+ "Remove corresponding part in Simulink model, signals probablty lead to terminators."
566
+ )
567
+ exit_codes_messages = [
568
+ missing_producer,
569
+ missing_consumer,
570
+ incorrect_csv,
571
+ never_active_signals,
572
+ ]
573
+ exit_code_bit_field = [
574
+ bool(int(b)) for b in bin(exit_code)[2:]
575
+ ] # [2:] gets rid of 0b part
576
+ for idx, value in enumerate(exit_code_bit_field):
577
+ if value:
578
+ LOGGER.info(exit_codes_messages[idx])
579
+
580
+ def aggregate_model_inports(self):
581
+ """Aggregate inport signals from models"""
582
+ used_inports = set()
583
+ for _, unit_cfg in self.per_unit_cfgs.items():
584
+ for _, inp in unit_cfg.items():
585
+ used_inports = used_inports.union(set(inp["inports"].keys()))
586
+ return used_inports
587
+
588
+ def aggregate_supplier_inports(self):
589
+ """Aggregate outports from signal interface (supplier inports)"""
590
+ external_outports = set()
591
+ for if_output in self.signal_ifs.values():
592
+ for signals in if_output.get_external_outputs().values():
593
+ external_outports = external_outports.union(set(signals.keys()))
594
+ return external_outports
595
+
596
+ def run(self, args):
597
+ """Run signal inconsistency check."""
598
+ exit_code = EXIT_CODE_OK
599
+ if self.models:
600
+ prj_cfs = get_project_configs(args.project_folders)
601
+ self.signal_ifs, self.per_unit_cfgs = get_signal_interfaces(
602
+ prj_cfs, self.models
603
+ )
604
+ self.never_active_signals = get_never_active_signals(prj_cfs, self.models)
605
+ LOGGER.info("Start check signal inconsistencies for: %s", self.models)
606
+ self.signal_inconsistency_results = check_inconsistency(
607
+ self.signal_ifs, self.never_active_signals
608
+ )
609
+ exit_code, log_output = self._analyse_inconsistency()
610
+ LOGGER.info("Finished check signal inconsistencies %s", log_output)
611
+ self._print_console_log_info(exit_code)
612
+ if args.report:
613
+ LOGGER.info(
614
+ "Start generating interface inconsistencies html-report for all projects"
615
+ )
616
+ gen_sig_incons_index_file(
617
+ list(self.signal_inconsistency_results.keys())
618
+ )
619
+ self._create_reports()
620
+ LOGGER.info("Finished - generating inconsistencies html-reports")
621
+ else:
622
+ LOGGER.info("No models in change")
623
+ return exit_code
624
+
625
+
626
+ def run_signal_inconsistency_check(args: argparse.Namespace) -> int:
627
+ """Create Signal Inconsistency instance and run checks."""
628
+ sig_in = SignalInconsistency(args)
629
+ return sig_in.run(args)
630
+
631
+
632
+ def main(argv: Optional[List[str]] = None) -> int:
633
+ """Create Signal Inconsistency instance and run checks."""
634
+ parser = argparse.ArgumentParser(description=PARSER_HELP)
635
+ configure_parser(parser)
636
+ args = parser.parse_args(argv)
637
+ return args.func(args)
638
+
639
+
640
+ if __name__ == "__main__":
641
+ sys.exit(main(sys.argv[1:]))