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,575 +1,575 @@
1
- # Copyright 2024 Volvo Car Corporation
2
- # Licensed under Apache 2.0.
3
-
4
- # -*- coding: utf-8 -*-
5
-
6
- """Python module used for calculating interfaces for CSP"""
7
-
8
-
9
- import argparse
10
- import os
11
- import re
12
- import sys
13
- from itertools import product
14
- from pathlib import Path
15
- from textwrap import dedent
16
- from typing import List, Optional
17
-
18
- import git
19
-
20
- from powertrain_build.interface.application import Application, Model, get_active_signals
21
- from powertrain_build.interface.ems import CsvEMS
22
- from powertrain_build.lib import logger
23
-
24
- LOGGER = logger.create_logger("Check interface")
25
-
26
-
27
- def process_app(config):
28
- """Get an app specification for the current project
29
-
30
- Entrypoint for external scripts.
31
-
32
- Args:
33
- config (pathlib.Path): Path to the ProjectCfg.json
34
- Returns:
35
- app (Application): pybuild project
36
- """
37
- app = Application()
38
- app.parse_definition(config)
39
- return app
40
-
41
-
42
- def model_app_consistency(model, app_models, app, errors):
43
- """Compare model signal interface with list of models.
44
-
45
- Args:
46
- model (Model): model to compare against application
47
- app_models (list(Model)): list of models to compare with
48
- app (Application): pybuild project
49
- errors (dict): Object for counting errors of different types
50
- """
51
-
52
- for compare_model in app_models:
53
- LOGGER.debug("Comparing %s with %s in %s", model.name, compare_model.name, app.name)
54
- active_model_outsignals = get_active_signals(model.outsignals, app.pybuild["feature_cfg"])
55
- active_model_insignals = get_active_signals(model.insignals, app.pybuild["feature_cfg"])
56
- active_compare_outsignals = get_active_signals(compare_model.outsignals, app.pybuild["feature_cfg"])
57
- active_compare_insignals = get_active_signals(compare_model.insignals, app.pybuild["feature_cfg"])
58
- check_signals(
59
- active_model_insignals,
60
- active_compare_outsignals,
61
- errors,
62
- [app.name, model.name],
63
- [app.name, compare_model.name],
64
- )
65
- check_signals(
66
- active_model_outsignals,
67
- active_compare_insignals,
68
- errors,
69
- [app.name, model.name],
70
- [app.name, compare_model.name],
71
- )
72
-
73
-
74
- def check_internal_signals(app, model_names=None):
75
- """Look for all internal signal mismatches.
76
-
77
- Args:
78
- app (Application): pybuild project
79
- model_names (list(Model)): models based on parsed config jsons
80
- Returns:
81
- serious_mismatch (bool): A serious mismatch was found
82
- """
83
- serious_mismatch = False
84
- LOGGER.debug("Checking internal signals")
85
- LOGGER.debug("Checking against %s", app.signals)
86
- errors = {"type": 0, "range": 0, "unit": 0, "width": 0}
87
- app_models = app.get_models()
88
- for signal in app.signals:
89
- LOGGER.debug(signal.properties)
90
-
91
- for model in app_models:
92
- if model_names is not None and model.name not in model_names:
93
- LOGGER.debug("Skipping %s", model.name)
94
- continue
95
- LOGGER.debug("Checking %s in %s", model.name, app.name)
96
- active_insignals = get_active_signals(model.insignals, app.pybuild["feature_cfg"])
97
- insignal_mismatch = check_signals(active_insignals, app.signals, errors, [app.name, model.name], [app.name])
98
- active_outsignals = get_active_signals(model.outsignals, app.pybuild["feature_cfg"])
99
- outsignal_mismatch = check_signals(active_outsignals, app.signals, errors, [app.name, model.name], [app.name])
100
- if insignal_mismatch or outsignal_mismatch:
101
- serious_mismatch = True
102
- model_app_consistency(model, app_models, app, errors)
103
- # Only compare with all models if a mismatch is found
104
- LOGGER.debug("Total errors: %s", errors)
105
- return serious_mismatch
106
-
107
-
108
- def check_models_generic(all_models, model_names, emses):
109
- """Check filtered models against all models and external interfaces."""
110
- serious_mismatch = False
111
- for model in all_models:
112
- LOGGER.info("Checking signals attributes for %s", model.name)
113
- if model.name not in model_names:
114
- continue
115
- errors = {"type": 0, "range": 0, "unit": 0, "width": 0}
116
- LOGGER.debug("Checking internal signals for %s", model.name)
117
- for corresponding_model in all_models:
118
- serious_mismatch |= check_signals(
119
- model.insignals, corresponding_model.outsignals, errors, [model.name], [corresponding_model.name]
120
- )
121
- serious_mismatch |= check_signals(
122
- model.outsignals, corresponding_model.insignals, errors, [model.name], [corresponding_model.name]
123
- )
124
- if emses:
125
- LOGGER.debug("Checking external signals for %s", model.name)
126
- for ems in emses:
127
- serious_mismatch |= check_signals(
128
- model.insignals, ems.outsignals, errors, [model.name], [ems.name]
129
- )
130
- serious_mismatch |= check_signals(
131
- model.outsignals, ems.insignals, errors, [model.name], [ems.name]
132
- )
133
- LOGGER.debug("Total errors for %s: %s", model.name, errors)
134
- return serious_mismatch
135
-
136
-
137
- def get_all_models(model_root):
138
- """Find, filter and parse all model configurations."""
139
- LOGGER.info("Parsing all models")
140
- prefix = "config_"
141
- suffix = ".json"
142
- models = []
143
- for dirpath, _, filenames in os.walk(model_root):
144
- dirpath = Path(dirpath)
145
- for filename in [f for f in filenames if f.startswith(prefix) and f.endswith(suffix)]:
146
- name = filename[len(prefix): -len(suffix)]
147
- if name == dirpath.parent.stem:
148
- model = Model(None)
149
- model.parse_definition((name, Path(dirpath, filename)))
150
- models.append(model)
151
- return models
152
-
153
-
154
- def get_projects(root, project_names):
155
- """Find, parse and filter all project configurations."""
156
- LOGGER.info("Parsing all projects")
157
- projects = []
158
- for dirpath, _, filenames in os.walk(root):
159
- dirpath = Path(dirpath)
160
- for filename in [f for f in filenames if f == "ProjectCfg.json"]:
161
- config = Path(dirpath, filename)
162
- app = Application()
163
- app_name = app.get_name(config)
164
- if project_names is not None and app_name not in project_names:
165
- if config.parent.stem not in project_names:
166
- LOGGER.info("%s or %s does not match %s", app_name, config.parent.stem, project_names)
167
- continue
168
- app.parse_definition(config)
169
- if app.pybuild["build_cfg"].has_yaml_interface:
170
- LOGGER.warning("Interface checks for yaml-interface projects are not implemtented yet")
171
- LOGGER.info("Adding empty interface for %s", app_name)
172
- projects.append((app, None))
173
- else:
174
- ems = CsvEMS()
175
- ems.parse_definition(config)
176
- projects.append((app, ems))
177
- return projects
178
-
179
-
180
- def correct_type(left_spec, right_spec):
181
- """Check if the type is the same in two specifications.
182
-
183
- Args:
184
- left_spec (dict): Signal specification
185
- right_spec (dict): Signal specification to compare with
186
- Returns:
187
- matches (bool): Spec1 and Spec2 has the same type
188
- """
189
- return left_spec["type"] == right_spec["type"]
190
-
191
-
192
- def correct_attribute(left_spec, right_spec, attribute, default=None, check_bool=True):
193
- """Check attributes other than type.
194
-
195
- Args:
196
- left_spec (dict): Signal specification
197
- right_spec (dict): Signal specification to compare with
198
- attribute (string): Attribute to check
199
- default (value): Default value for the attribute (default: None)
200
- check_bool (bool): Check signals of type Bool (default: True)
201
- Returns:
202
- matches (bool): Spec1 and Spec2 has the same value for the attribute
203
- """
204
-
205
- def _format(value):
206
- if isinstance(value, str):
207
- value = value.strip()
208
- if re.fullmatch("[+-]?[0-9]+", value):
209
- value = int(value)
210
- elif re.fullmatch("[+-]?[0-9]+[0-9.,eE+]*", value):
211
- value = float(value.replace(",", "."))
212
- return value
213
-
214
- if not check_bool and left_spec["type"] == "Bool":
215
- return True
216
- return _format(left_spec.get(attribute, default)) == _format(right_spec.get(attribute, default))
217
-
218
-
219
- def found_mismatch(name, left_spec, right_spec, attribute, left_path, right_path):
220
- """Handle finding a mismatch.
221
-
222
- Args:
223
- name (string): Name of signal
224
- left_spec (dict): Spec of signal
225
- right_spec (dict): Signal specification to compare with
226
- attribute (string): Attribute to check
227
- left_path (list(str)): Path for where the left signals' definitions come from
228
- right_path (list(str)): Path for where the right signals' definitions come from
229
- """
230
- if attribute in ["type", "width"]:
231
- # TODO: Add more properties as serious when the interfaces are more cleaned up
232
- LOGGER.error(
233
- "%s has %ss: %s in %s and %s in %s",
234
- name,
235
- attribute,
236
- left_spec.get(attribute),
237
- left_path,
238
- right_spec.get(attribute),
239
- right_path,
240
- )
241
- return True
242
- LOGGER.info(
243
- "%s has %ss: %s in %s and %s in %s",
244
- name,
245
- attribute,
246
- left_spec.get(attribute),
247
- left_path,
248
- right_spec.get(attribute),
249
- right_path,
250
- )
251
- return False
252
-
253
-
254
- def check_external_signals(ems, app, model_names=None):
255
- """Look for external signal mismatches.
256
-
257
- Args:
258
- ems (CsvEMS): Parsed signal interface cvs:s
259
- app (Application): Parsed project config
260
- model_names (list(Model)): models based on parsed config jsons
261
- Returns:
262
- serious_mismatch (bool): A serious mismatch was found
263
- """
264
- serious_mismatch = False
265
-
266
- LOGGER.debug("Checking insignals")
267
- errors = {"type": 0, "range": 0, "unit": 0, "width": 0}
268
- app_models = app.get_models()
269
- for model in app_models:
270
- if model_names is not None and model.name not in model_names:
271
- LOGGER.debug("Skipping %s in %s", model.name, app.name)
272
- continue
273
- LOGGER.debug("Checking %s in %s", model.name, app.name)
274
- serious_mismatch |= check_signals(
275
- get_active_signals(model.insignals, app.pybuild["feature_cfg"]),
276
- ems.outsignals,
277
- errors,
278
- [app.name, model.name],
279
- [ems.name],
280
- )
281
- serious_mismatch |= check_signals(
282
- get_active_signals(model.outsignals, app.pybuild["feature_cfg"]),
283
- ems.insignals,
284
- errors,
285
- [app.name, model.name],
286
- [ems.name],
287
- )
288
- LOGGER.debug("Total errors: %s", errors)
289
- return serious_mismatch
290
-
291
-
292
- def check_signals(left_signals, right_signals, errors, left_path=None, right_path=None):
293
- """Compares insignals from one system with the outsignals of another.
294
-
295
- Args:
296
- left_signals (list(Signal)): Insignals of one system such as a model
297
- right_signals (list(Signal)): Outsignals of system to compare with
298
- errors (dict): Object for counting errors of different types
299
- left_path (list(str)): Path for where the left signals' definitions come from
300
- right_path (list(str)): Path for where the right signals' definitions come from
301
- Returns:
302
- serious_mismatch (bool): A serious mismatch was found
303
- """
304
- left_path = [] if left_path is None else left_path
305
- right_path = [] if right_path is None else right_path
306
- serious_mismatch = False
307
- LOGGER.debug("Checking from %s", left_signals)
308
- LOGGER.debug("Checking against %s", right_signals)
309
- for (left_signal, right_signal) in [
310
- (left, right) for left, right in product(left_signals, right_signals) if left.name == right.name
311
- ]:
312
- LOGGER.debug("Comparing %s and %s", left_signal, right_signal)
313
- left_properties = left_signal.properties
314
- right_properties = right_signal.properties
315
- LOGGER.debug("Properties left: %s", left_properties)
316
- LOGGER.debug("Properties right: %s", right_properties)
317
- if not correct_type(left_properties, right_properties):
318
- serious_mismatch |= found_mismatch(
319
- left_signal.name, left_properties, right_properties, "type", left_path, right_path
320
- )
321
- errors["type"] += 1
322
- if not correct_attribute(left_properties, right_properties, "min", check_bool=False):
323
- serious_mismatch |= found_mismatch(
324
- left_signal.name, left_properties, right_properties, "min", left_path, right_path
325
- )
326
- errors["range"] += 1
327
- if not correct_attribute(left_properties, right_properties, "max", check_bool=False):
328
- serious_mismatch |= found_mismatch(
329
- left_signal.name, left_properties, right_properties, "max", left_path, right_path
330
- )
331
- errors["range"] += 1
332
- if not correct_attribute(left_properties, right_properties, "unit", default="", check_bool=False):
333
- serious_mismatch |= found_mismatch(
334
- left_signal.name, left_properties, right_properties, "unit", left_path, right_path
335
- )
336
- errors["unit"] += 1
337
- if not correct_attribute(left_properties, right_properties, "width", default=1):
338
- serious_mismatch |= found_mismatch(
339
- left_signal.name, left_properties, right_properties, "width", left_path, right_path
340
- )
341
- errors["width"] += 1
342
- return serious_mismatch
343
-
344
-
345
- PARSER_HELP = dedent(r"""
346
- Checks attributes and existence of signals
347
-
348
- Produced but not consumed signals are giving warnings
349
- Consumed but not produced signals are giving errors
350
-
351
- Attributes checked are: types, ranges, units and widths
352
- Mismatches in types or widths give errors
353
- Mismatches in min, max or unit gives warnings
354
-
355
- Examples:
356
- py -3.6 -m powertrain_build.check_interface models_in_projects <Projects> <Models/ModelGroup>\
357
- --projects <ProjectOne> <ProjectTwo>
358
- Checks models in Models/ModelGroup against ProjectOne and ProjectTwo in the folder Projects
359
-
360
- py -3.6 -m powertrain_build.check_interface models <Models> --models <ModelOne> <ModelTwo>
361
- Checks models ModelOne and ModelTwo against all other models in the folder Models
362
-
363
- py -3.6 -m powertrain_build.check_interface projects <Projects> \
364
- --projects ProjectOne ProjectTwo ProjectThree
365
- Checks the interfaces of ProjectOne, ProjectTwo and ProjectThree in the folder Projects
366
- """).strip()
367
-
368
-
369
- def configure_parser(parser: argparse.ArgumentParser):
370
- """Configure arguments in parser."""
371
- subparsers = parser.add_subparsers(
372
- help="help for subcommand",
373
- dest="mode",
374
- required=True,
375
- )
376
-
377
- # create the parser for the different commands
378
- model = subparsers.add_parser(
379
- "models",
380
- description=dedent("""
381
- Check models independently of projects.
382
-
383
- All signals are assumed to be active.
384
- Any signal that gives and error is used in a model but is not produced in any model or project
385
- interface.
386
- """).strip(),
387
- )
388
- add_model_args(model)
389
- model.set_defaults(func=model_check)
390
-
391
- project = subparsers.add_parser(
392
- "projects",
393
- description=dedent("""
394
- Check projects as a whole.
395
-
396
- It checks all models intenally and the SPM vs the interface.
397
- """).strip(),
398
- )
399
- add_project_args(project)
400
- project.set_defaults(func=projects_check)
401
-
402
- models_in_projects = subparsers.add_parser(
403
- "models_in_projects",
404
- description=dedent("""
405
- Check models specifically for projects.
406
-
407
- Codeswitches are used to determine if the signals are produced and consumed in each model.
408
- """).strip(),
409
- )
410
- add_project_args(models_in_projects)
411
- add_model_args(models_in_projects)
412
- models_in_projects.add_argument("--properties", help="Check properties such as type", action="store_true")
413
- models_in_projects.add_argument("--existence", help="Check signal existence consistency", action="store_true")
414
- models_in_projects.set_defaults(func=models_in_projects_check)
415
-
416
-
417
- def add_project_args(parser: argparse.ArgumentParser):
418
- """Add project arguments to subparser"""
419
- parser.add_argument("project_root", help="Path to start looking for projects", type=Path)
420
- parser.add_argument(
421
- "--projects", help="Name of projects to check. Matches both path and interface name.", nargs="+"
422
- )
423
-
424
-
425
- def add_model_args(parser: argparse.ArgumentParser):
426
- """Add model arguments to subparser"""
427
- parser.add_argument("model_root", help="Path to start looking for models", type=Path)
428
- parser.add_argument("--models", help="Name of models to check", nargs="+")
429
- parser.add_argument("--gerrit", action="store_true", help="Deprecated")
430
- parser.add_argument("--git", action="store_true", help="Get models to check from git HEAD")
431
-
432
-
433
- def get_changed_models():
434
- """Get changed models in current commit."""
435
- repo = git.Repo()
436
- changed_files_tmp = repo.git.diff("--diff-filter=d", "--name-only", "HEAD~1")
437
- changed_files = changed_files_tmp.splitlines()
438
- changed_models = [m for m in changed_files if m.endswith(".mdl") or m.endswith(".slx")]
439
- return changed_models
440
-
441
-
442
- def model_path_to_name(model_paths):
443
- """Extract model names from a list of model paths."""
444
- model_names = []
445
- for model_path in model_paths:
446
- model_name_with_extension = model_path.split("/")[-1]
447
- model_name = model_name_with_extension.split(".")[0]
448
- model_names.append(model_name)
449
- return model_names
450
-
451
-
452
- def model_check(args: argparse.Namespace):
453
- """Entry point for models command."""
454
- serious_mismatch = False
455
- all_models = get_all_models(args.model_root)
456
- if args.models is not None:
457
- model_names = args.models
458
- elif args.git or args.gerrit:
459
- # Still checking args.gerrit due to common-linux-signal_consistency in pt-zuul-jobs
460
- model_paths = get_changed_models()
461
- model_names = model_path_to_name(model_paths)
462
- else:
463
- model_names = [model.name for model in all_models]
464
-
465
- serious_mismatch |= check_models_generic(all_models, model_names, [])
466
-
467
- if serious_mismatch:
468
- LOGGER.error("Serious interface errors found.")
469
-
470
- return serious_mismatch
471
-
472
-
473
- def projects_check(args: argparse.Namespace):
474
- """Entry point for projects command."""
475
- serious_mismatch = False
476
- projects = get_projects(args.project_root, args.projects)
477
- for app, ems in projects:
478
- LOGGER.info("Checking interfaces for %s", app.name)
479
- serious_mismatch |= check_internal_signals(app, None)
480
- if ems is not None:
481
- serious_mismatch |= check_external_signals(ems, app, None)
482
-
483
- if serious_mismatch:
484
- LOGGER.error("Serious interface errors found.")
485
-
486
- return serious_mismatch
487
-
488
-
489
- def models_in_projects_check(args: argparse.Namespace):
490
- """Entry point for models_in_projects command."""
491
- serious_mismatch = False
492
- projects = get_projects(args.project_root, args.projects)
493
- LOGGER.debug("Checking projects: %s", projects)
494
- if args.properties:
495
- for app, ems in projects:
496
- serious_mismatch |= check_internal_signals(app, None or args.models)
497
- if ems is not None:
498
- serious_mismatch |= check_external_signals(ems, app, None or args.models)
499
- if args.existence:
500
- all_models = get_all_models(args.model_root)
501
- model_names = [model.name for model in all_models] if args.models is None else args.models
502
- serious_mismatch |= signal_existence(projects, model_names)
503
-
504
- if serious_mismatch:
505
- LOGGER.error("Serious interface errors found.")
506
-
507
- return serious_mismatch
508
-
509
-
510
- def signal_existence(projects, model_names):
511
- """Check which signals are consumed and produced in each project."""
512
- serious_mismatch = False
513
- for app, ems in projects:
514
- app_models = app.get_models()
515
- LOGGER.info("Checking %s", app.name)
516
- for project_model in app_models:
517
- if project_model.name not in model_names:
518
- continue
519
- LOGGER.debug("Checking signal existence for %s", project_model.name)
520
- active_insignals = get_active_signals(project_model.insignals, app.pybuild["feature_cfg"])
521
- active_outsignals = get_active_signals(project_model.outsignals, app.pybuild["feature_cfg"])
522
- insignal_matches = {}
523
- outsignal_matches = {}
524
- for check_model in app_models:
525
- signal_match(
526
- active_insignals,
527
- get_active_signals(check_model.outsignals, app.pybuild["feature_cfg"]),
528
- insignal_matches,
529
- )
530
- signal_match(
531
- active_outsignals,
532
- get_active_signals(check_model.insignals, app.pybuild["feature_cfg"]),
533
- outsignal_matches,
534
- )
535
- if ems is not None:
536
- signal_match(active_insignals, ems.outsignals, insignal_matches)
537
- signal_match(active_outsignals, ems.insignals, outsignal_matches)
538
- for missing_signal in [signal for signal, matched in insignal_matches.items() if not matched]:
539
- # serious_mismatch = True # TODO: Activate this code when we want to gate on it.
540
- LOGGER.warning(
541
- "%s is consumed in %s but never produced in %s", missing_signal, project_model.name, app.name
542
- )
543
- for missing_signal in [signal for signal, matched in insignal_matches.items() if not matched]:
544
- LOGGER.debug("%s is consumed in %s and produced in %s", missing_signal, project_model.name, app.name)
545
- for missing_signal in [signal for signal, matched in outsignal_matches.items() if not matched]:
546
- LOGGER.info(
547
- "%s is produced in %s but never consumed in %s", missing_signal, project_model.name, app.name
548
- )
549
- for missing_signal in [signal for signal, matched in outsignal_matches.items() if not matched]:
550
- LOGGER.debug("%s is consumed in %s and produced in %s", missing_signal, project_model.name, app.name)
551
- return serious_mismatch
552
-
553
-
554
- def signal_match(signals_to_check, signals_to_check_against, matches):
555
- """Check for matches in signal names."""
556
- for a_signal in signals_to_check:
557
- matches[a_signal.name] = matches.get(a_signal.name, False)
558
- for b_signal in signals_to_check_against:
559
- if b_signal.name == a_signal.name:
560
- matches[a_signal.name] = True
561
-
562
-
563
- def main(argv: Optional[List[str]] = None):
564
- """Main function for stand alone execution."""
565
- parser = argparse.ArgumentParser(
566
- description=PARSER_HELP,
567
- formatter_class=argparse.RawTextHelpFormatter,
568
- )
569
- configure_parser(parser)
570
- args = parser.parse_args(argv)
571
- args.func(args)
572
-
573
-
574
- if __name__ == "__main__":
575
- main(sys.argv[1:])
1
+ # Copyright 2024 Volvo Car Corporation
2
+ # Licensed under Apache 2.0.
3
+
4
+ # -*- coding: utf-8 -*-
5
+
6
+ """Python module used for calculating interfaces for CSP"""
7
+
8
+
9
+ import argparse
10
+ import os
11
+ import re
12
+ import sys
13
+ from itertools import product
14
+ from pathlib import Path
15
+ from textwrap import dedent
16
+ from typing import List, Optional
17
+
18
+ import git
19
+
20
+ from powertrain_build.interface.application import Application, Model, get_active_signals
21
+ from powertrain_build.interface.ems import CsvEMS
22
+ from powertrain_build.lib import logger
23
+
24
+ LOGGER = logger.create_logger("Check interface")
25
+
26
+
27
+ def process_app(config):
28
+ """Get an app specification for the current project
29
+
30
+ Entrypoint for external scripts.
31
+
32
+ Args:
33
+ config (pathlib.Path): Path to the ProjectCfg.json
34
+ Returns:
35
+ app (Application): pybuild project
36
+ """
37
+ app = Application()
38
+ app.parse_definition(config)
39
+ return app
40
+
41
+
42
+ def model_app_consistency(model, app_models, app, errors):
43
+ """Compare model signal interface with list of models.
44
+
45
+ Args:
46
+ model (Model): model to compare against application
47
+ app_models (list(Model)): list of models to compare with
48
+ app (Application): pybuild project
49
+ errors (dict): Object for counting errors of different types
50
+ """
51
+
52
+ for compare_model in app_models:
53
+ LOGGER.debug("Comparing %s with %s in %s", model.name, compare_model.name, app.name)
54
+ active_model_outsignals = get_active_signals(model.outsignals, app.pybuild["feature_cfg"])
55
+ active_model_insignals = get_active_signals(model.insignals, app.pybuild["feature_cfg"])
56
+ active_compare_outsignals = get_active_signals(compare_model.outsignals, app.pybuild["feature_cfg"])
57
+ active_compare_insignals = get_active_signals(compare_model.insignals, app.pybuild["feature_cfg"])
58
+ check_signals(
59
+ active_model_insignals,
60
+ active_compare_outsignals,
61
+ errors,
62
+ [app.name, model.name],
63
+ [app.name, compare_model.name],
64
+ )
65
+ check_signals(
66
+ active_model_outsignals,
67
+ active_compare_insignals,
68
+ errors,
69
+ [app.name, model.name],
70
+ [app.name, compare_model.name],
71
+ )
72
+
73
+
74
+ def check_internal_signals(app, model_names=None):
75
+ """Look for all internal signal mismatches.
76
+
77
+ Args:
78
+ app (Application): pybuild project
79
+ model_names (list(Model)): models based on parsed config jsons
80
+ Returns:
81
+ serious_mismatch (bool): A serious mismatch was found
82
+ """
83
+ serious_mismatch = False
84
+ LOGGER.debug("Checking internal signals")
85
+ LOGGER.debug("Checking against %s", app.signals)
86
+ errors = {"type": 0, "range": 0, "unit": 0, "width": 0}
87
+ app_models = app.get_models()
88
+ for signal in app.signals:
89
+ LOGGER.debug(signal.properties)
90
+
91
+ for model in app_models:
92
+ if model_names is not None and model.name not in model_names:
93
+ LOGGER.debug("Skipping %s", model.name)
94
+ continue
95
+ LOGGER.debug("Checking %s in %s", model.name, app.name)
96
+ active_insignals = get_active_signals(model.insignals, app.pybuild["feature_cfg"])
97
+ insignal_mismatch = check_signals(active_insignals, app.signals, errors, [app.name, model.name], [app.name])
98
+ active_outsignals = get_active_signals(model.outsignals, app.pybuild["feature_cfg"])
99
+ outsignal_mismatch = check_signals(active_outsignals, app.signals, errors, [app.name, model.name], [app.name])
100
+ if insignal_mismatch or outsignal_mismatch:
101
+ serious_mismatch = True
102
+ model_app_consistency(model, app_models, app, errors)
103
+ # Only compare with all models if a mismatch is found
104
+ LOGGER.debug("Total errors: %s", errors)
105
+ return serious_mismatch
106
+
107
+
108
+ def check_models_generic(all_models, model_names, emses):
109
+ """Check filtered models against all models and external interfaces."""
110
+ serious_mismatch = False
111
+ for model in all_models:
112
+ LOGGER.info("Checking signals attributes for %s", model.name)
113
+ if model.name not in model_names:
114
+ continue
115
+ errors = {"type": 0, "range": 0, "unit": 0, "width": 0}
116
+ LOGGER.debug("Checking internal signals for %s", model.name)
117
+ for corresponding_model in all_models:
118
+ serious_mismatch |= check_signals(
119
+ model.insignals, corresponding_model.outsignals, errors, [model.name], [corresponding_model.name]
120
+ )
121
+ serious_mismatch |= check_signals(
122
+ model.outsignals, corresponding_model.insignals, errors, [model.name], [corresponding_model.name]
123
+ )
124
+ if emses:
125
+ LOGGER.debug("Checking external signals for %s", model.name)
126
+ for ems in emses:
127
+ serious_mismatch |= check_signals(
128
+ model.insignals, ems.outsignals, errors, [model.name], [ems.name]
129
+ )
130
+ serious_mismatch |= check_signals(
131
+ model.outsignals, ems.insignals, errors, [model.name], [ems.name]
132
+ )
133
+ LOGGER.debug("Total errors for %s: %s", model.name, errors)
134
+ return serious_mismatch
135
+
136
+
137
+ def get_all_models(model_root):
138
+ """Find, filter and parse all model configurations."""
139
+ LOGGER.info("Parsing all models")
140
+ prefix = "config_"
141
+ suffix = ".json"
142
+ models = []
143
+ for dirpath, _, filenames in os.walk(model_root):
144
+ dirpath = Path(dirpath)
145
+ for filename in [f for f in filenames if f.startswith(prefix) and f.endswith(suffix)]:
146
+ name = filename[len(prefix): -len(suffix)]
147
+ if name == dirpath.parent.stem:
148
+ model = Model(None)
149
+ model.parse_definition((name, Path(dirpath, filename)))
150
+ models.append(model)
151
+ return models
152
+
153
+
154
+ def get_projects(root, project_names):
155
+ """Find, parse and filter all project configurations."""
156
+ LOGGER.info("Parsing all projects")
157
+ projects = []
158
+ for dirpath, _, filenames in os.walk(root):
159
+ dirpath = Path(dirpath)
160
+ for filename in [f for f in filenames if f == "ProjectCfg.json"]:
161
+ config = Path(dirpath, filename)
162
+ app = Application()
163
+ app_name = app.get_name(config)
164
+ if project_names is not None and app_name not in project_names:
165
+ if config.parent.stem not in project_names:
166
+ LOGGER.info("%s or %s does not match %s", app_name, config.parent.stem, project_names)
167
+ continue
168
+ app.parse_definition(config)
169
+ if app.pybuild["build_cfg"].has_yaml_interface:
170
+ LOGGER.warning("Interface checks for yaml-interface projects are not implemtented yet")
171
+ LOGGER.info("Adding empty interface for %s", app_name)
172
+ projects.append((app, None))
173
+ else:
174
+ ems = CsvEMS()
175
+ ems.parse_definition(config)
176
+ projects.append((app, ems))
177
+ return projects
178
+
179
+
180
+ def correct_type(left_spec, right_spec):
181
+ """Check if the type is the same in two specifications.
182
+
183
+ Args:
184
+ left_spec (dict): Signal specification
185
+ right_spec (dict): Signal specification to compare with
186
+ Returns:
187
+ matches (bool): Spec1 and Spec2 has the same type
188
+ """
189
+ return left_spec["type"] == right_spec["type"]
190
+
191
+
192
+ def correct_attribute(left_spec, right_spec, attribute, default=None, check_bool=True):
193
+ """Check attributes other than type.
194
+
195
+ Args:
196
+ left_spec (dict): Signal specification
197
+ right_spec (dict): Signal specification to compare with
198
+ attribute (string): Attribute to check
199
+ default (value): Default value for the attribute (default: None)
200
+ check_bool (bool): Check signals of type Bool (default: True)
201
+ Returns:
202
+ matches (bool): Spec1 and Spec2 has the same value for the attribute
203
+ """
204
+
205
+ def _format(value):
206
+ if isinstance(value, str):
207
+ value = value.strip()
208
+ if re.fullmatch("[+-]?[0-9]+", value):
209
+ value = int(value)
210
+ elif re.fullmatch("[+-]?[0-9]+[0-9.,eE+]*", value):
211
+ value = float(value.replace(",", "."))
212
+ return value
213
+
214
+ if not check_bool and left_spec["type"] == "Bool":
215
+ return True
216
+ return _format(left_spec.get(attribute, default)) == _format(right_spec.get(attribute, default))
217
+
218
+
219
+ def found_mismatch(name, left_spec, right_spec, attribute, left_path, right_path):
220
+ """Handle finding a mismatch.
221
+
222
+ Args:
223
+ name (string): Name of signal
224
+ left_spec (dict): Spec of signal
225
+ right_spec (dict): Signal specification to compare with
226
+ attribute (string): Attribute to check
227
+ left_path (list(str)): Path for where the left signals' definitions come from
228
+ right_path (list(str)): Path for where the right signals' definitions come from
229
+ """
230
+ if attribute in ["type", "width"]:
231
+ # TODO: Add more properties as serious when the interfaces are more cleaned up
232
+ LOGGER.error(
233
+ "%s has %ss: %s in %s and %s in %s",
234
+ name,
235
+ attribute,
236
+ left_spec.get(attribute),
237
+ left_path,
238
+ right_spec.get(attribute),
239
+ right_path,
240
+ )
241
+ return True
242
+ LOGGER.info(
243
+ "%s has %ss: %s in %s and %s in %s",
244
+ name,
245
+ attribute,
246
+ left_spec.get(attribute),
247
+ left_path,
248
+ right_spec.get(attribute),
249
+ right_path,
250
+ )
251
+ return False
252
+
253
+
254
+ def check_external_signals(ems, app, model_names=None):
255
+ """Look for external signal mismatches.
256
+
257
+ Args:
258
+ ems (CsvEMS): Parsed signal interface cvs:s
259
+ app (Application): Parsed project config
260
+ model_names (list(Model)): models based on parsed config jsons
261
+ Returns:
262
+ serious_mismatch (bool): A serious mismatch was found
263
+ """
264
+ serious_mismatch = False
265
+
266
+ LOGGER.debug("Checking insignals")
267
+ errors = {"type": 0, "range": 0, "unit": 0, "width": 0}
268
+ app_models = app.get_models()
269
+ for model in app_models:
270
+ if model_names is not None and model.name not in model_names:
271
+ LOGGER.debug("Skipping %s in %s", model.name, app.name)
272
+ continue
273
+ LOGGER.debug("Checking %s in %s", model.name, app.name)
274
+ serious_mismatch |= check_signals(
275
+ get_active_signals(model.insignals, app.pybuild["feature_cfg"]),
276
+ ems.outsignals,
277
+ errors,
278
+ [app.name, model.name],
279
+ [ems.name],
280
+ )
281
+ serious_mismatch |= check_signals(
282
+ get_active_signals(model.outsignals, app.pybuild["feature_cfg"]),
283
+ ems.insignals,
284
+ errors,
285
+ [app.name, model.name],
286
+ [ems.name],
287
+ )
288
+ LOGGER.debug("Total errors: %s", errors)
289
+ return serious_mismatch
290
+
291
+
292
+ def check_signals(left_signals, right_signals, errors, left_path=None, right_path=None):
293
+ """Compares insignals from one system with the outsignals of another.
294
+
295
+ Args:
296
+ left_signals (list(Signal)): Insignals of one system such as a model
297
+ right_signals (list(Signal)): Outsignals of system to compare with
298
+ errors (dict): Object for counting errors of different types
299
+ left_path (list(str)): Path for where the left signals' definitions come from
300
+ right_path (list(str)): Path for where the right signals' definitions come from
301
+ Returns:
302
+ serious_mismatch (bool): A serious mismatch was found
303
+ """
304
+ left_path = [] if left_path is None else left_path
305
+ right_path = [] if right_path is None else right_path
306
+ serious_mismatch = False
307
+ LOGGER.debug("Checking from %s", left_signals)
308
+ LOGGER.debug("Checking against %s", right_signals)
309
+ for (left_signal, right_signal) in [
310
+ (left, right) for left, right in product(left_signals, right_signals) if left.name == right.name
311
+ ]:
312
+ LOGGER.debug("Comparing %s and %s", left_signal, right_signal)
313
+ left_properties = left_signal.properties
314
+ right_properties = right_signal.properties
315
+ LOGGER.debug("Properties left: %s", left_properties)
316
+ LOGGER.debug("Properties right: %s", right_properties)
317
+ if not correct_type(left_properties, right_properties):
318
+ serious_mismatch |= found_mismatch(
319
+ left_signal.name, left_properties, right_properties, "type", left_path, right_path
320
+ )
321
+ errors["type"] += 1
322
+ if not correct_attribute(left_properties, right_properties, "min", check_bool=False):
323
+ serious_mismatch |= found_mismatch(
324
+ left_signal.name, left_properties, right_properties, "min", left_path, right_path
325
+ )
326
+ errors["range"] += 1
327
+ if not correct_attribute(left_properties, right_properties, "max", check_bool=False):
328
+ serious_mismatch |= found_mismatch(
329
+ left_signal.name, left_properties, right_properties, "max", left_path, right_path
330
+ )
331
+ errors["range"] += 1
332
+ if not correct_attribute(left_properties, right_properties, "unit", default="", check_bool=False):
333
+ serious_mismatch |= found_mismatch(
334
+ left_signal.name, left_properties, right_properties, "unit", left_path, right_path
335
+ )
336
+ errors["unit"] += 1
337
+ if not correct_attribute(left_properties, right_properties, "width", default=1):
338
+ serious_mismatch |= found_mismatch(
339
+ left_signal.name, left_properties, right_properties, "width", left_path, right_path
340
+ )
341
+ errors["width"] += 1
342
+ return serious_mismatch
343
+
344
+
345
+ PARSER_HELP = dedent(r"""
346
+ Checks attributes and existence of signals
347
+
348
+ Produced but not consumed signals are giving warnings
349
+ Consumed but not produced signals are giving errors
350
+
351
+ Attributes checked are: types, ranges, units and widths
352
+ Mismatches in types or widths give errors
353
+ Mismatches in min, max or unit gives warnings
354
+
355
+ Examples:
356
+ py -3.6 -m powertrain_build.check_interface models_in_projects <Projects> <Models/ModelGroup>\
357
+ --projects <ProjectOne> <ProjectTwo>
358
+ Checks models in Models/ModelGroup against ProjectOne and ProjectTwo in the folder Projects
359
+
360
+ py -3.6 -m powertrain_build.check_interface models <Models> --models <ModelOne> <ModelTwo>
361
+ Checks models ModelOne and ModelTwo against all other models in the folder Models
362
+
363
+ py -3.6 -m powertrain_build.check_interface projects <Projects> \
364
+ --projects ProjectOne ProjectTwo ProjectThree
365
+ Checks the interfaces of ProjectOne, ProjectTwo and ProjectThree in the folder Projects
366
+ """).strip()
367
+
368
+
369
+ def configure_parser(parser: argparse.ArgumentParser):
370
+ """Configure arguments in parser."""
371
+ subparsers = parser.add_subparsers(
372
+ help="help for subcommand",
373
+ dest="mode",
374
+ required=True,
375
+ )
376
+
377
+ # create the parser for the different commands
378
+ model = subparsers.add_parser(
379
+ "models",
380
+ description=dedent("""
381
+ Check models independently of projects.
382
+
383
+ All signals are assumed to be active.
384
+ Any signal that gives and error is used in a model but is not produced in any model or project
385
+ interface.
386
+ """).strip(),
387
+ )
388
+ add_model_args(model)
389
+ model.set_defaults(func=model_check)
390
+
391
+ project = subparsers.add_parser(
392
+ "projects",
393
+ description=dedent("""
394
+ Check projects as a whole.
395
+
396
+ It checks all models intenally and the SPM vs the interface.
397
+ """).strip(),
398
+ )
399
+ add_project_args(project)
400
+ project.set_defaults(func=projects_check)
401
+
402
+ models_in_projects = subparsers.add_parser(
403
+ "models_in_projects",
404
+ description=dedent("""
405
+ Check models specifically for projects.
406
+
407
+ Codeswitches are used to determine if the signals are produced and consumed in each model.
408
+ """).strip(),
409
+ )
410
+ add_project_args(models_in_projects)
411
+ add_model_args(models_in_projects)
412
+ models_in_projects.add_argument("--properties", help="Check properties such as type", action="store_true")
413
+ models_in_projects.add_argument("--existence", help="Check signal existence consistency", action="store_true")
414
+ models_in_projects.set_defaults(func=models_in_projects_check)
415
+
416
+
417
+ def add_project_args(parser: argparse.ArgumentParser):
418
+ """Add project arguments to subparser"""
419
+ parser.add_argument("project_root", help="Path to start looking for projects", type=Path)
420
+ parser.add_argument(
421
+ "--projects", help="Name of projects to check. Matches both path and interface name.", nargs="+"
422
+ )
423
+
424
+
425
+ def add_model_args(parser: argparse.ArgumentParser):
426
+ """Add model arguments to subparser"""
427
+ parser.add_argument("model_root", help="Path to start looking for models", type=Path)
428
+ parser.add_argument("--models", help="Name of models to check", nargs="+")
429
+ parser.add_argument("--gerrit", action="store_true", help="Deprecated")
430
+ parser.add_argument("--git", action="store_true", help="Get models to check from git HEAD")
431
+
432
+
433
+ def get_changed_models():
434
+ """Get changed models in current commit."""
435
+ repo = git.Repo()
436
+ changed_files_tmp = repo.git.diff("--diff-filter=d", "--name-only", "HEAD~1")
437
+ changed_files = changed_files_tmp.splitlines()
438
+ changed_models = [m for m in changed_files if m.endswith(".mdl") or m.endswith(".slx")]
439
+ return changed_models
440
+
441
+
442
+ def model_path_to_name(model_paths):
443
+ """Extract model names from a list of model paths."""
444
+ model_names = []
445
+ for model_path in model_paths:
446
+ model_name_with_extension = model_path.split("/")[-1]
447
+ model_name = model_name_with_extension.split(".")[0]
448
+ model_names.append(model_name)
449
+ return model_names
450
+
451
+
452
+ def model_check(args: argparse.Namespace):
453
+ """Entry point for models command."""
454
+ serious_mismatch = False
455
+ all_models = get_all_models(args.model_root)
456
+ if args.models is not None:
457
+ model_names = args.models
458
+ elif args.git or args.gerrit:
459
+ # Still checking args.gerrit due to common-linux-signal_consistency in pt-zuul-jobs
460
+ model_paths = get_changed_models()
461
+ model_names = model_path_to_name(model_paths)
462
+ else:
463
+ model_names = [model.name for model in all_models]
464
+
465
+ serious_mismatch |= check_models_generic(all_models, model_names, [])
466
+
467
+ if serious_mismatch:
468
+ LOGGER.error("Serious interface errors found.")
469
+
470
+ return serious_mismatch
471
+
472
+
473
+ def projects_check(args: argparse.Namespace):
474
+ """Entry point for projects command."""
475
+ serious_mismatch = False
476
+ projects = get_projects(args.project_root, args.projects)
477
+ for app, ems in projects:
478
+ LOGGER.info("Checking interfaces for %s", app.name)
479
+ serious_mismatch |= check_internal_signals(app, None)
480
+ if ems is not None:
481
+ serious_mismatch |= check_external_signals(ems, app, None)
482
+
483
+ if serious_mismatch:
484
+ LOGGER.error("Serious interface errors found.")
485
+
486
+ return serious_mismatch
487
+
488
+
489
+ def models_in_projects_check(args: argparse.Namespace):
490
+ """Entry point for models_in_projects command."""
491
+ serious_mismatch = False
492
+ projects = get_projects(args.project_root, args.projects)
493
+ LOGGER.debug("Checking projects: %s", projects)
494
+ if args.properties:
495
+ for app, ems in projects:
496
+ serious_mismatch |= check_internal_signals(app, None or args.models)
497
+ if ems is not None:
498
+ serious_mismatch |= check_external_signals(ems, app, None or args.models)
499
+ if args.existence:
500
+ all_models = get_all_models(args.model_root)
501
+ model_names = [model.name for model in all_models] if args.models is None else args.models
502
+ serious_mismatch |= signal_existence(projects, model_names)
503
+
504
+ if serious_mismatch:
505
+ LOGGER.error("Serious interface errors found.")
506
+
507
+ return serious_mismatch
508
+
509
+
510
+ def signal_existence(projects, model_names):
511
+ """Check which signals are consumed and produced in each project."""
512
+ serious_mismatch = False
513
+ for app, ems in projects:
514
+ app_models = app.get_models()
515
+ LOGGER.info("Checking %s", app.name)
516
+ for project_model in app_models:
517
+ if project_model.name not in model_names:
518
+ continue
519
+ LOGGER.debug("Checking signal existence for %s", project_model.name)
520
+ active_insignals = get_active_signals(project_model.insignals, app.pybuild["feature_cfg"])
521
+ active_outsignals = get_active_signals(project_model.outsignals, app.pybuild["feature_cfg"])
522
+ insignal_matches = {}
523
+ outsignal_matches = {}
524
+ for check_model in app_models:
525
+ signal_match(
526
+ active_insignals,
527
+ get_active_signals(check_model.outsignals, app.pybuild["feature_cfg"]),
528
+ insignal_matches,
529
+ )
530
+ signal_match(
531
+ active_outsignals,
532
+ get_active_signals(check_model.insignals, app.pybuild["feature_cfg"]),
533
+ outsignal_matches,
534
+ )
535
+ if ems is not None:
536
+ signal_match(active_insignals, ems.outsignals, insignal_matches)
537
+ signal_match(active_outsignals, ems.insignals, outsignal_matches)
538
+ for missing_signal in [signal for signal, matched in insignal_matches.items() if not matched]:
539
+ # serious_mismatch = True # TODO: Activate this code when we want to gate on it.
540
+ LOGGER.warning(
541
+ "%s is consumed in %s but never produced in %s", missing_signal, project_model.name, app.name
542
+ )
543
+ for missing_signal in [signal for signal, matched in insignal_matches.items() if not matched]:
544
+ LOGGER.debug("%s is consumed in %s and produced in %s", missing_signal, project_model.name, app.name)
545
+ for missing_signal in [signal for signal, matched in outsignal_matches.items() if not matched]:
546
+ LOGGER.info(
547
+ "%s is produced in %s but never consumed in %s", missing_signal, project_model.name, app.name
548
+ )
549
+ for missing_signal in [signal for signal, matched in outsignal_matches.items() if not matched]:
550
+ LOGGER.debug("%s is consumed in %s and produced in %s", missing_signal, project_model.name, app.name)
551
+ return serious_mismatch
552
+
553
+
554
+ def signal_match(signals_to_check, signals_to_check_against, matches):
555
+ """Check for matches in signal names."""
556
+ for a_signal in signals_to_check:
557
+ matches[a_signal.name] = matches.get(a_signal.name, False)
558
+ for b_signal in signals_to_check_against:
559
+ if b_signal.name == a_signal.name:
560
+ matches[a_signal.name] = True
561
+
562
+
563
+ def main(argv: Optional[List[str]] = None):
564
+ """Main function for stand alone execution."""
565
+ parser = argparse.ArgumentParser(
566
+ description=PARSER_HELP,
567
+ formatter_class=argparse.RawTextHelpFormatter,
568
+ )
569
+ configure_parser(parser)
570
+ args = parser.parse_args(argv)
571
+ args.func(args)
572
+
573
+
574
+ if __name__ == "__main__":
575
+ main(sys.argv[1:])