opencos-eda 0.2.50__tar.gz → 0.2.52__tar.gz

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 (100) hide show
  1. {opencos_eda-0.2.50/opencos_eda.egg-info → opencos_eda-0.2.52}/PKG-INFO +2 -1
  2. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/lec.py +7 -4
  3. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/shell.py +11 -8
  4. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/sim.py +38 -22
  5. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/waves.py +12 -2
  6. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/deps/deps_file.py +67 -14
  7. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda.py +28 -3
  8. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_base.py +39 -18
  9. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_config.py +1 -0
  10. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_config_defaults.yml +38 -14
  11. opencos_eda-0.2.52/opencos/eda_deps_sanitize.py +73 -0
  12. opencos_eda-0.2.52/opencos/tests/test_tools.py +350 -0
  13. opencos_eda-0.2.52/opencos/tools/cocotb.py +483 -0
  14. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/questa_fse.py +2 -0
  15. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/verilator.py +31 -0
  16. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/yosys.py +21 -5
  17. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/util.py +30 -5
  18. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/utils/markup_helpers.py +31 -2
  19. opencos_eda-0.2.52/opencos/utils/status_constants.py +27 -0
  20. opencos_eda-0.2.52/opencos/utils/vscode_helper.py +47 -0
  21. opencos_eda-0.2.52/opencos/utils/vsim_helper.py +55 -0
  22. {opencos_eda-0.2.50 → opencos_eda-0.2.52/opencos_eda.egg-info}/PKG-INFO +2 -1
  23. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos_eda.egg-info/SOURCES.txt +5 -0
  24. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos_eda.egg-info/entry_points.txt +1 -0
  25. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos_eda.egg-info/requires.txt +1 -0
  26. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/pyproject.toml +4 -1
  27. opencos_eda-0.2.50/opencos/tests/test_tools.py +0 -156
  28. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/LICENSE +0 -0
  29. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/LICENSE.spdx +0 -0
  30. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/README.md +0 -0
  31. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/__init__.py +0 -0
  32. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/_version.py +0 -0
  33. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/_waves_pkg.sv +0 -0
  34. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/__init__.py +0 -0
  35. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/build.py +0 -0
  36. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/elab.py +0 -0
  37. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/export.py +0 -0
  38. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/flist.py +0 -0
  39. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/multi.py +0 -0
  40. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/open.py +0 -0
  41. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/proj.py +0 -0
  42. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/sweep.py +0 -0
  43. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/synth.py +0 -0
  44. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/targets.py +0 -0
  45. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/upload.py +0 -0
  46. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/deps/__init__.py +0 -0
  47. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/deps/defaults.py +0 -0
  48. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/deps/deps_commands.py +0 -0
  49. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/deps/deps_processor.py +0 -0
  50. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/deps_schema.py +0 -0
  51. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_config_max_verilator_waivers.yml +0 -0
  52. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_config_reduced.yml +0 -0
  53. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_deps_bash_completion.bash +0 -0
  54. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_extract_targets.py +0 -0
  55. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_tool_helper.py +0 -0
  56. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/export_helper.py +0 -0
  57. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/export_json_convert.py +0 -0
  58. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/files.py +0 -0
  59. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/hw/__init__.py +0 -0
  60. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/hw/oc_cli.py +0 -0
  61. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/hw/pcie.py +0 -0
  62. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/names.py +0 -0
  63. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/peakrdl_cleanup.py +0 -0
  64. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/seed.py +0 -0
  65. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/__init__.py +0 -0
  66. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/custom_config.yml +0 -0
  67. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/command_order/DEPS.yml +0 -0
  68. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/error_msgs/DEPS.yml +0 -0
  69. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -0
  70. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
  71. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -0
  72. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -0
  73. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -0
  74. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/helpers.py +0 -0
  75. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_build.py +0 -0
  76. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_deps_helpers.py +0 -0
  77. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_deps_schema.py +0 -0
  78. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_eda.py +0 -0
  79. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_eda_elab.py +0 -0
  80. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_eda_synth.py +0 -0
  81. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_oc_cli.py +0 -0
  82. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/__init__.py +0 -0
  83. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/invio.py +0 -0
  84. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/invio_helpers.py +0 -0
  85. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/invio_yosys.py +0 -0
  86. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/iverilog.py +0 -0
  87. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/modelsim_ase.py +0 -0
  88. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/questa.py +0 -0
  89. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/riviera.py +0 -0
  90. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/slang.py +0 -0
  91. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/slang_yosys.py +0 -0
  92. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/surelog.py +0 -0
  93. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/tabbycad_yosys.py +0 -0
  94. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/vivado.py +0 -0
  95. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/utils/__init__.py +0 -0
  96. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/utils/str_helpers.py +0 -0
  97. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/utils/subprocess_helpers.py +0 -0
  98. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos_eda.egg-info/dependency_links.txt +0 -0
  99. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos_eda.egg-info/top_level.txt +0 -0
  100. {opencos_eda-0.2.50 → opencos_eda-0.2.52}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencos-eda
3
- Version: 0.2.50
3
+ Version: 0.2.52
4
4
  Summary: A simple Python package for wrapping RTL simuliatons and synthesis
5
5
  Author-email: Simon Sabato <simon@cognichip.ai>, Drew Ranck <drew@cognichip.ai>
6
6
  Project-URL: Homepage, https://github.com/cognichip/opencos
@@ -15,4 +15,5 @@ Requires-Dist: schema>=0.7.7
15
15
  Requires-Dist: toml>=0.10.2
16
16
  Requires-Dist: yamllint>=1.35.1
17
17
  Requires-Dist: PySerial>=3.5
18
+ Requires-Dist: cocotb>=2.0.0b1
18
19
  Dynamic: license-file
@@ -69,16 +69,13 @@ class CommandLec(CommandDesign):
69
69
  if self.status_any_error():
70
70
  return unparsed
71
71
 
72
- # add defines for this job type
73
- if not self.args['top']:
74
- self.args['top'] = 'eda.lec'
75
-
76
72
  # we require there to be two --designs set.
77
73
  if not self.args['designs'] and len(self.args['designs']) != 2:
78
74
  self.error('Requires two designs via --designs=<target1> --designs=<target2>',
79
75
  f'designs={self.args["designs"]}')
80
76
  return []
81
77
 
78
+
82
79
  # Before we do anything else, make sure the two designs actually exist.
83
80
  for design in self.args['designs']:
84
81
  # maybe it's a file?
@@ -89,6 +86,12 @@ class CommandLec(CommandDesign):
89
86
  else:
90
87
  self.error(f'--designs={design}, value is not a file or target')
91
88
 
89
+ if not self.args['top']:
90
+ # Correct the 'top' name so it's eda.<target1>.<target2>.lec (shortnames)
91
+ _, short_target1 = os.path.split(self.args['designs'][0])
92
+ _, short_target2 = os.path.split(self.args['designs'][1])
93
+ self.args['top'] = f'eda.{short_target1}.{short_target2}'
94
+
92
95
  # create our work dir
93
96
  self.create_work_dir()
94
97
  self.run_dep_commands()
@@ -16,6 +16,8 @@ import os
16
16
  from opencos import util, export_helper
17
17
  from opencos.eda_base import CommandDesign
18
18
 
19
+ from opencos.utils import status_constants
20
+
19
21
 
20
22
  class CommandShell(CommandDesign):
21
23
  '''Base class command handler for: eda sim ...'''
@@ -181,7 +183,6 @@ class CommandShell(CommandDesign):
181
183
  _must_strings.append(self.args['pass-pattern'])
182
184
 
183
185
  if len(_bad_strings) > 0 or len(_must_strings) > 0:
184
- hit_bad_string = False
185
186
  hit_must_string_dict = dict.fromkeys(_must_strings)
186
187
  fname = os.path.join(self.args['work-dir'], filename)
187
188
  with open(fname, 'r', encoding='utf-8') as f:
@@ -191,12 +192,14 @@ class CommandShell(CommandDesign):
191
192
  if k in line:
192
193
  hit_must_string_dict[k] = True
193
194
  if any(bad_str in line for bad_str in _bad_strings):
194
- hit_bad_string = True
195
- self.error(f"log {fname}:{lineno} contains one of {_bad_strings=}")
195
+ self.error(
196
+ f"log {fname}:{lineno} contains one of {_bad_strings=}",
197
+ error_code=status_constants.EDA_SHELL_LOG_HAS_BAD_STRING
198
+ )
196
199
 
197
- if hit_bad_string:
198
- self.status += 1
199
200
  if any(x is None for x in hit_must_string_dict.values()):
200
- self.error(f"Didn't get all passing patterns in log {fname}: {_must_strings=}",
201
- f" {hit_must_string_dict=}")
202
- self.status += 1
201
+ self.error(
202
+ f"Didn't get all passing patterns in log {fname}: {_must_strings=}",
203
+ f" {hit_must_string_dict=}",
204
+ error_code=status_constants.EDA_SHELL_LOG_MISSING_MUST_STRING
205
+ )
@@ -15,6 +15,7 @@ import os
15
15
 
16
16
  from opencos import util, export_helper
17
17
  from opencos.eda_base import CommandDesign, Tool
18
+ from opencos.utils import status_constants
18
19
 
19
20
  class CommandSim(CommandDesign):
20
21
  '''Base class command handler for: eda sim ...'''
@@ -170,11 +171,14 @@ class CommandSim(CommandDesign):
170
171
  if log_filename:
171
172
  log_fname = log_filename
172
173
 
173
- self.exec(work_dir=self.args['work-dir'], command_list=clist, tee_fpath=tee_fpath)
174
+ _, stdout, _ = self.exec(
175
+ work_dir=self.args['work-dir'], command_list=clist, tee_fpath=tee_fpath
176
+ )
174
177
 
175
178
  if check_logs and log_fname:
176
179
  self.check_logs_for_errors(
177
- filename=log_fname, bad_strings=bad_strings, must_strings=must_strings,
180
+ filename='', file_contents_str=stdout,
181
+ bad_strings=bad_strings, must_strings=must_strings,
178
182
  use_bad_strings=use_bad_strings, use_must_strings=use_must_strings
179
183
  )
180
184
  if log_fname:
@@ -273,8 +277,8 @@ class CommandSim(CommandDesign):
273
277
  '''
274
278
  return
275
279
 
276
- def check_logs_for_errors( # pylint: disable=dangerous-default-value
277
- self, filename: str,
280
+ def check_logs_for_errors( # pylint: disable=dangerous-default-value,too-many-locals,too-many-branches
281
+ self, filename: str = '', file_contents_str: str = '',
278
282
  bad_strings: list = [], must_strings: list = [],
279
283
  use_bad_strings: bool = True, use_must_strings: bool = True
280
284
  ) -> None:
@@ -294,26 +298,38 @@ class CommandSim(CommandDesign):
294
298
  if self.args['pass-pattern'] != "":
295
299
  _must_strings.append(self.args['pass-pattern'])
296
300
 
297
- if len(_bad_strings) > 0 or len(_must_strings) > 0:
298
- hit_bad_string = False
299
- hit_must_string_dict = dict.fromkeys(_must_strings)
301
+ if len(_bad_strings) == 0 and len(_must_strings) == 0:
302
+ return
303
+
304
+ hit_must_string_dict = dict.fromkeys(_must_strings)
305
+
306
+ lines = []
307
+ fname = ''
308
+ if file_contents_str:
309
+ lines = file_contents_str.split('\n')
310
+ elif filename:
300
311
  fname = os.path.join(self.args['work-dir'], filename)
301
312
  with open(fname, 'r', encoding='utf-8') as f:
302
- for lineno, line in enumerate(f):
303
- if any(must_str in line for must_str in _must_strings):
304
- for k, _ in hit_must_string_dict.items():
305
- if k in line:
306
- hit_must_string_dict[k] = True
307
- if any(bad_str in line for bad_str in _bad_strings):
308
- hit_bad_string = True
309
- self.error(f"log {fname}:{lineno} contains one of {_bad_strings=}")
310
-
311
- if hit_bad_string:
312
- self.status += 1
313
- if any(x is None for x in hit_must_string_dict.values()):
314
- self.error(f"Didn't get all passing patterns in log {fname}: {_must_strings=}",
315
- f" {hit_must_string_dict=}")
316
- self.status += 1
313
+ lines = f.readl.splitlines()
314
+
315
+ if lines:
316
+ for lineno, line in enumerate(lines):
317
+ if any(must_str in line for must_str in _must_strings):
318
+ for k, _ in hit_must_string_dict.items():
319
+ if k in line:
320
+ hit_must_string_dict[k] = True
321
+ if any(bad_str in line for bad_str in _bad_strings):
322
+ self.error(
323
+ f"log {fname}:{lineno} contains one of {_bad_strings=}",
324
+ error_code=status_constants.EDA_SIM_LOG_HAS_BAD_STRING
325
+ )
326
+
327
+ if any(x is None for x in hit_must_string_dict.values()):
328
+ self.error(
329
+ f"Didn't get all passing patterns in log {fname}: {_must_strings=}",
330
+ f" {hit_must_string_dict=}",
331
+ error_code=status_constants.EDA_SIM_LOG_MISSING_MUST_STRING
332
+ )
317
333
 
318
334
  # Methods that derived classes must override:
319
335
 
@@ -32,6 +32,8 @@ class CommandWaves(CommandDesign):
32
32
 
33
33
  VSIM_TOOLS = set([
34
34
  'questa',
35
+ 'questa_fse',
36
+ 'riviera',
35
37
  'modelsim_ase',
36
38
  ])
37
39
 
@@ -137,13 +139,21 @@ class CommandWaves(CommandDesign):
137
139
  self.error(f"Don't know how to open {wave_file} without one of",
138
140
  f"{self.VSIM_TOOLS} in PATH")
139
141
  elif wave_file.endswith('.fst'):
140
- if 'gtkwave' in self.config['tools_loaded'] and shutil.which('gtkwave'):
142
+ if ('vaporview' in self.config['tools_loaded'] or \
143
+ 'surfer' in self.config['tools_loaded']) and shutil.which('code'):
144
+ command_list = ['code', '-n', '.', wave_file]
145
+ self.exec(os.path.dirname(wave_file), command_list)
146
+ elif 'gtkwave' in self.config['tools_loaded'] and shutil.which('gtkwave'):
141
147
  command_list = ['gtkwave', wave_file]
142
148
  self.exec(os.path.dirname(wave_file), command_list)
143
149
  else:
144
150
  self.error(f"Don't know how to open {wave_file} without GtkWave in PATH")
145
151
  elif wave_file.endswith('.vcd'):
146
- if 'gtkwave' in self.config['tools_loaded'] and shutil.which('gtkwave'):
152
+ if ('vaporview' in self.config['tools_loaded'] or \
153
+ 'surfer' in self.config['tools_loaded']) and shutil.which('code'):
154
+ command_list = ['code', '-n', '.', wave_file]
155
+ self.exec(os.path.dirname(wave_file), command_list)
156
+ elif 'gtkwave' in self.config['tools_loaded'] and shutil.which('gtkwave'):
147
157
  command_list = ['gtkwave', wave_file]
148
158
  self.exec(os.path.dirname(wave_file), command_list)
149
159
  elif self._vsim_available(from_tools=self.VSIM_VCD_TOOLS):
@@ -10,15 +10,20 @@ from pathlib import Path
10
10
 
11
11
  import toml
12
12
 
13
+ from opencos import util
13
14
  from opencos.deps.defaults import DEPS_FILE_EXTS, ROOT_TABLE_KEYS_NOT_TARGETS
14
15
  from opencos.util import debug, error
15
- from opencos.utils.markup_helpers import yaml_safe_load, toml_load_only_root_line_numbers
16
+ from opencos.utils.markup_helpers import yaml_safe_load, toml_load_only_root_line_numbers, \
17
+ markup_writer, markup_dumper
16
18
  from opencos.utils.str_helpers import fnmatch_or_re, dep_str2list
17
19
  from opencos.utils.subprocess_helpers import subprocess_run_background
20
+ from opencos.utils.status_constants import EDA_DEPS_FILE_NOT_FOUND, EDA_DEPS_TARGET_NOT_FOUND
18
21
 
19
22
 
20
23
  def deps_data_get_all_targets(data: dict) -> list:
21
24
  '''Given extracted DEPS data (dict) get all the root level keys that aren't defaults'''
25
+ if data is None:
26
+ return []
22
27
  return [x for x in data.keys() if x not in ROOT_TABLE_KEYS_NOT_TARGETS]
23
28
 
24
29
 
@@ -166,32 +171,43 @@ def deps_target_get_deps_list(
166
171
  ret = []
167
172
  for dep in deps:
168
173
  if isinstance(dep, str):
169
- if dep.startswith('#') or dep == '':
174
+ if not dep or dep.startswith('#'):
170
175
  continue
171
176
  ret.append(dep)
172
177
  return ret
173
178
 
174
179
 
175
180
  def deps_list_target_sanitize(
176
- entry, default_key: str = 'deps', target_node: str = '', deps_file: str = ''
181
+ entry, default_key: str = 'deps', target_node: str = '', deps_file: str = '',
182
+ entry_fix_deps_key: bool = True
177
183
  ) -> dict:
178
184
  '''Returns a sanitized DEPS markup table entry (dict --> dict)
179
185
 
180
186
  Since we support target entries that can be dict, list, or str(), sanitize
181
187
  them so they are a dict, with a key named 'deps' that has a list of deps.
182
188
  '''
189
+ ret = None
183
190
  if isinstance(entry, dict):
184
- return entry
191
+ ret = entry
185
192
 
186
193
  if isinstance(entry, str):
187
194
  mylist = dep_str2list(entry) # convert str to list
188
- return {default_key: mylist}
195
+ ret = {default_key: mylist}
189
196
 
190
197
  if isinstance(entry, list):
191
198
  # it's already a list
192
- return {default_key: entry}
199
+ ret = {default_key: entry}
200
+
201
+ if ret is not None:
202
+ if entry_fix_deps_key and 'deps' in ret:
203
+ ret['deps'] = deps_target_get_deps_list(
204
+ entry=ret, default_key='deps', deps_file=deps_file,
205
+ entry_must_have_default_key=True
206
+ )
207
+ else:
208
+ assert False, f"Can't convert to list {entry=} {default_key=} {target_node=} {deps_file=}"
193
209
 
194
- assert False, f"Can't convert to list {entry=} {default_key=} {target_node=} {deps_file=}"
210
+ return ret
195
211
 
196
212
 
197
213
  class DepsFile:
@@ -230,7 +246,10 @@ class DepsFile:
230
246
  if deps_path and os.path.exists(deps_path):
231
247
  self.rel_deps_file = os.path.join(os.path.relpath(deps_path), deps_leaf)
232
248
 
233
- self.error = command_design_ref.error # method.
249
+ self.error = getattr(command_design_ref, 'error', None)
250
+ if not self.error:
251
+ self.error = util.error
252
+
234
253
 
235
254
  def found(self) -> bool:
236
255
  '''Returns true if this DEPS file exists and extracted non-empty data'''
@@ -270,15 +289,18 @@ class DepsFile:
270
289
  if '.' in target_node:
271
290
  # Likely a filename (target_node does not include path)
272
291
  self.error(f'Trying to resolve command-line target={t_full} (file?):',
273
- f'File={t_node} not found in directory={t_path}')
292
+ f'File={t_node} not found in directory={t_path}',
293
+ error_code=EDA_DEPS_FILE_NOT_FOUND)
274
294
  elif not self.rel_deps_file:
275
295
  # target, but there's no DEPS file
276
296
  self.error(f'Trying to resolve command-line target={t_full}:',
277
- f'but path {t_path} has no DEPS markup file (DEPS.yml)')
297
+ f'but path {t_path} has no DEPS markup file (DEPS.yml)',
298
+ error_code=EDA_DEPS_FILE_NOT_FOUND)
278
299
  else:
279
300
  self.error(f'Trying to resolve command-line target={t_full}:',
280
301
  f'was not found in deps_file={self.rel_deps_file},',
281
- f'possible targets in deps file = {list(self.data.keys())}')
302
+ f'possible targets in deps file = {list(self.data.keys())}',
303
+ error_code=EDA_DEPS_TARGET_NOT_FOUND)
282
304
  else:
283
305
  # If we have caller_info, then this was a recursive call from another
284
306
  # DEPS file. It should already have the useful error messaging:
@@ -287,16 +309,19 @@ class DepsFile:
287
309
  # Likely a filename (target_node does not include path)
288
310
  self.error(f'Trying to resolve target={t_full} (file?):',
289
311
  f'called from {caller_info},',
290
- f'File={t_node} not found in directory={t_path}')
312
+ f'File={t_node} not found in directory={t_path}',
313
+ error_code=EDA_DEPS_FILE_NOT_FOUND)
291
314
  elif not self.rel_deps_file:
292
315
  # target, but there's no DEPS file
293
316
  self.error(f'Trying to resolve target={t_full}:',
294
317
  f'called from {caller_info},',
295
- f'but {t_path} has no DEPS markup file (DEPS.yml)')
318
+ f'but {t_path} has no DEPS markup file (DEPS.yml)',
319
+ error_code=EDA_DEPS_FILE_NOT_FOUND)
296
320
  else:
297
321
  self.error(f'Trying to resolve target={t_full}:',
298
322
  f'called from {caller_info},',
299
- f'Target not found in deps_file={self.rel_deps_file}')
323
+ f'Target not found in deps_file={self.rel_deps_file}',
324
+ error_code=EDA_DEPS_TARGET_NOT_FOUND)
300
325
  else:
301
326
  debug(f'Found {target_node=} in deps_file={self.rel_deps_file}')
302
327
  found_target = True
@@ -324,3 +349,31 @@ class DepsFile:
324
349
  entry.update(entry_sanitized)
325
350
 
326
351
  return entry
352
+
353
+ def get_all_targets(self) -> list:
354
+ '''Returns list of all targets in this obj, skipping keys like DEFAULTS and METADATA'''
355
+ return deps_data_get_all_targets(self.data)
356
+
357
+ def get_sanitized_data(self) -> dict:
358
+ '''Returns a sanitized dict of self.data, resolves DEFAULTS, METADATA, and implicit
359
+
360
+ space-separated-str, lists for 'deps' in a target entry
361
+ '''
362
+ new_data = {}
363
+ targets = self.get_all_targets()
364
+ for target in targets:
365
+ new_data[target] = self.get_entry(target)
366
+ return new_data
367
+
368
+ def write_sanitized_markup(self, filepath: str) -> None:
369
+ '''Writes the sanitized dict of self.data as JSON/YAML (filepath extension)'''
370
+ markup_writer(data=self.get_sanitized_data(), filepath=filepath)
371
+
372
+ def str_sanitized_markup(self, as_yaml: bool = False) -> str:
373
+ '''Returns str sanitized YAML or JSON str of self.data'''
374
+ return markup_dumper(self.get_sanitized_data(), as_yaml=as_yaml)
375
+
376
+
377
+ def print_sanitized_markup(self, as_yaml: bool = False) -> None:
378
+ '''Prints sanitized JSON str of self.data to STDOUT'''
379
+ print(self.str_sanitized_markup(as_yaml=as_yaml))
@@ -16,9 +16,12 @@ import argparse
16
16
  import shlex
17
17
  import importlib.util
18
18
 
19
+ from pathlib import Path
20
+
19
21
  import opencos
20
22
  from opencos import util, eda_config, eda_base
21
23
  from opencos.eda_base import Tool, which_tool
24
+ from opencos.utils import vsim_helper, vscode_helper
22
25
 
23
26
  # Configure util:
24
27
  util.progname = "EDA"
@@ -151,6 +154,7 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
151
154
  If so, updates config['auto_tools_order'][tool]['exe']
152
155
  '''
153
156
 
157
+
154
158
  tool = eda_config.update_config_auto_tool_order_for_tool(
155
159
  tool=tool, config=config
156
160
  )
@@ -192,16 +196,36 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
192
196
  has_all_exe = True
193
197
  has_all_in_exe_path = True
194
198
  for exe in exe_list:
195
- assert exe != '', f'{tool=} {value=} value missing "exe" {exe=}'
199
+ assert exe != '', f'{name=} {value=} value missing "exe" {exe=}'
196
200
  p = shutil.which(exe)
197
201
  if not p:
198
202
  has_all_exe = False
199
203
  util.debug(f"... No, missing exe {exe}")
200
204
  for req in value.get('requires_in_exe_path', []):
201
- if p and req and str(req).lower() not in str(p).lower():
205
+ if p and req and str(Path(req)) not in str(Path(p)):
202
206
  has_all_in_exe_path = False
203
207
  util.debug(f"... No, missing path requirement {req}")
204
208
 
209
+ has_vsim_helper = True
210
+ if value.get('requires_vsim_helper', False):
211
+ # This tool name must be in opencos.utils.vsim_helper.TOOL_IS[name].
212
+ # Special case for vsim being used by a lot of tools.
213
+ vsim_helper.init() # only runs checks once internally
214
+ has_vsim_helper = vsim_helper.TOOL_IS.get(name, False)
215
+
216
+ has_vscode_helper = True
217
+ needs_vscode_extensions = value.get('requires_vscode_extension', None)
218
+ if needs_vscode_extensions:
219
+ if not isinstance(needs_vscode_extensions, list):
220
+ util.error(
221
+ f'eda config issue, tool {name}: requires_vscode_extension must be a list'
222
+ )
223
+ else:
224
+ vscode_helper.init() # only runs checks once internally
225
+ has_vscode_helper = all(
226
+ x in vscode_helper.EXTENSIONS for x in needs_vscode_extensions
227
+ )
228
+
205
229
  if has_all_exe:
206
230
  requires_cmd_list = value.get('requires_cmd', [])
207
231
  for cmd in requires_cmd_list:
@@ -219,7 +243,8 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
219
243
  util.debug(f"... No, exception {e} running {cmd_list}")
220
244
 
221
245
 
222
- if all([has_all_py, has_all_env, has_all_exe, has_all_in_exe_path]):
246
+ if all((has_all_py, has_all_env, has_all_exe, has_all_in_exe_path,
247
+ has_vsim_helper, has_vscode_helper)):
223
248
  exe = exe_list[0]
224
249
  p = shutil.which(exe)
225
250
  config['auto_tools_found'][name] = exe # populate key-value pairs w/ first exe in list
@@ -28,6 +28,7 @@ from opencos.util import Colors
28
28
  from opencos.utils.str_helpers import sprint_time, strip_outer_quotes, string_or_space, \
29
29
  indent_wrap_long_text
30
30
  from opencos.utils.subprocess_helpers import subprocess_run_background
31
+ from opencos.utils import status_constants
31
32
 
32
33
  from opencos.deps.deps_file import DepsFile, deps_data_get_all_targets
33
34
  from opencos.deps.deps_processor import DepsProcessor
@@ -274,7 +275,7 @@ class Command:
274
275
  file=self.errors_log_f
275
276
  )
276
277
 
277
- self.status = util.error(*args, **kwargs)
278
+ self.status = util.error(*args, **kwargs) # error_code passed and returned via kwargs
278
279
 
279
280
  def status_any_error(self, report=True) -> bool:
280
281
  '''Used by derived classes process_tokens() to know an error was reached
@@ -416,10 +417,16 @@ class Command:
416
417
  shell=shell
417
418
  )
418
419
 
419
- if return_code:
420
- self.status += return_code
420
+ if return_code > 0:
421
+ if return_code == 1:
422
+ self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE1
423
+ if return_code == 255:
424
+ self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE255
425
+ else:
426
+ self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE2
421
427
  if stop_on_error:
422
- self.error(f"exec: returned with error (return code: {return_code})")
428
+ self.error(f"exec: returned with error (return code: {return_code})",
429
+ error_code=self.status)
423
430
  else:
424
431
  util.debug(f"exec: returned with error (return code: {return_code})")
425
432
  else:
@@ -437,7 +444,8 @@ class Command:
437
444
  # Do some minimal type handling, preserving the type(self.args[key])
438
445
  if key not in self.args:
439
446
  self.error(f'set_arg, {key=} not in self.args {value=}',
440
- f'({self.command_name=}, {self.__class__.__name__=})')
447
+ f'({self.command_name=}, {self.__class__.__name__=})',
448
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
441
449
 
442
450
  cur_value = self.args[key]
443
451
 
@@ -568,7 +576,8 @@ class Command:
568
576
  parsed, unparsed = parser.parse_known_args(tokens + [''])
569
577
  unparsed = list(filter(None, unparsed))
570
578
  except argparse.ArgumentError:
571
- self.error(f'problem {self.command_name=} attempting to parse_known_args for {tokens=}')
579
+ self.error(f'problem {self.command_name=} attempting to parse_known_args for {tokens=}',
580
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
572
581
 
573
582
  parsed_as_dict = vars(parsed)
574
583
 
@@ -630,7 +639,8 @@ class Command:
630
639
  _, unparsed = self.run_argparser_on_list(tokens)
631
640
  if process_all and len(unparsed) > 0:
632
641
  self.error(f"Didn't understand argument: '{unparsed=}' in",
633
- f" {self.command_name=} context, {pwd=}")
642
+ f" {self.command_name=} context, {pwd=}",
643
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
634
644
 
635
645
  return unparsed
636
646
 
@@ -650,7 +660,8 @@ class Command:
650
660
 
651
661
  if not ret and error_if_no_command:
652
662
  self.error(f"Looking for a valid eda {self.command_name} <command>",
653
- f"but didn't find one in {tokens=}")
663
+ f"but didn't find one in {tokens=}",
664
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
654
665
  return ret
655
666
 
656
667
 
@@ -692,7 +703,8 @@ class Command:
692
703
  process_tokens(..) is the starting entrypoint from eda.py'''
693
704
  self.write_eda_config_and_args()
694
705
  self.error(f"No tool bound to command '{self.command_name}', you",
695
- " probably need to setup tool, or use '--tool <name>'")
706
+ " probably need to setup tool, or use '--tool <name>'",
707
+ error_code=status_constants.EDA_CONFIG_ERROR)
696
708
 
697
709
  def command_safe_set_tool_defines(self) -> None:
698
710
  '''Safe wrapper for calling self.set_tool_defines() in case a Tool parent class is
@@ -1006,7 +1018,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1006
1018
  self.defines[k] = v
1007
1019
  util.debug(f"Defined {k}={v}")
1008
1020
  return None
1009
- self.error(f"Didn't understand +define+: '{plusarg}'")
1021
+ self.error(f"Didn't understand +define+: '{plusarg}'",
1022
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1010
1023
  return None
1011
1024
 
1012
1025
  if plusarg.startswith('+incdir+'):
@@ -1018,7 +1031,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1018
1031
  self.incdirs.append(os.path.abspath(incdir))
1019
1032
  util.debug(f"Added include dir '{os.path.abspath(incdir)}'")
1020
1033
  return None
1021
- self.error(f"Didn't understand +incdir+: '{plusarg}'")
1034
+ self.error(f"Didn't understand +incdir+: '{plusarg}'",
1035
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1022
1036
  return None
1023
1037
 
1024
1038
  # remaining plusargs as stored in self.args['unprocessed-plusargs'] (list)
@@ -1032,7 +1046,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1032
1046
  # derived classes have the option to handle it
1033
1047
  return plusarg
1034
1048
 
1035
- self.error(f"Didn't understand +plusarg: '{plusarg}'")
1049
+ self.error(f"Didn't understand +plusarg: '{plusarg}'",
1050
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1036
1051
  return None
1037
1052
 
1038
1053
 
@@ -1224,7 +1239,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1224
1239
  found_target = True
1225
1240
  break # move on to the next target
1226
1241
  if not found_target: # if STILL not found_this_target...
1227
- self.error(f"Unable to resolve {target=}")
1242
+ self.error(f"Unable to resolve {target=}",
1243
+ error_code=status_constants.EDA_DEPS_TARGET_NOT_FOUND)
1228
1244
 
1229
1245
  # if we've found any target since being called, it means we found the one we were called for
1230
1246
  return found_target
@@ -1354,7 +1370,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1354
1370
  if len(unparsed) == 0:
1355
1371
  # If unparsed is still empty, then error.
1356
1372
  self.error(f"For command '{self.command_name}' no files or targets were",
1357
- f"presented at the command line: {orig_tokens}")
1373
+ f"presented at the command line: {orig_tokens}",
1374
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1358
1375
 
1359
1376
  # by this point hopefully this is a target ... is it a simple filename?
1360
1377
  remove_list = []
@@ -1408,7 +1425,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1408
1425
 
1409
1426
  # we were unable to figure out what this command line token is for...
1410
1427
  if process_all and len(unparsed) > 0:
1411
- self.error(f"Didn't understand command remaining tokens {unparsed=} in CommandDesign")
1428
+ self.error(f"Didn't understand command remaining tokens {unparsed=} in CommandDesign",
1429
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1412
1430
 
1413
1431
  # handle a missing self.args['top'] with last filepath or last target:
1414
1432
  if not self.args.get('top', ''):
@@ -1441,7 +1459,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1441
1459
 
1442
1460
  if self.error_on_missing_top and not self.args.get('top', ''):
1443
1461
  self.error("Did not get a --top or DEPS top, required to run command",
1444
- f"'{self.command_name}' for tool={self.args.get('tool', None)}")
1462
+ f"'{self.command_name}' for tool={self.args.get('tool', None)}",
1463
+ error_code=status_constants.EDA_COMMAND_MISSING_TOP)
1445
1464
 
1446
1465
  return unparsed
1447
1466
 
@@ -1943,7 +1962,8 @@ class CommandParallel(Command):
1943
1962
  bad_remaining_args = [x for x in single_cmd_unparsed if x.startswith('-')]
1944
1963
  if bad_remaining_args:
1945
1964
  self.error(f'for {self.command_name} {command=} the following args are unknown',
1946
- f'{bad_remaining_args}')
1965
+ f'{bad_remaining_args}',
1966
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1947
1967
 
1948
1968
  # Remove unparsed args starting with '+', since those are commonly sent downstream to
1949
1969
  # single job (example, CommandSim plusargs).
@@ -1985,7 +2005,8 @@ class CommandParallel(Command):
1985
2005
 
1986
2006
  key = get_job_arg(job_dict, arg_name='job-name')
1987
2007
  if not key:
1988
- self.error(f'{job_dict=} needs to have a --job-name= arg attached')
2008
+ self.error(f'{job_dict=} needs to have a --job-name= arg attached',
2009
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1989
2010
  if key not in job_names_count_dict:
1990
2011
  job_names_count_dict[key] = 1
1991
2012
  else:
@@ -46,6 +46,7 @@ class Defaults:
46
46
  supported_config_auto_tools_order_keys = set([
47
47
  'exe', 'handlers',
48
48
  'requires_env', 'requires_py', 'requires_cmd', 'requires_in_exe_path',
49
+ 'requires_vsim_helper', 'requires_vscode_extension',
49
50
  'disable-tools-multi',
50
51
  ])
51
52
  supported_config_tool_keys = set([