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.
- {opencos_eda-0.2.50/opencos_eda.egg-info → opencos_eda-0.2.52}/PKG-INFO +2 -1
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/lec.py +7 -4
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/shell.py +11 -8
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/sim.py +38 -22
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/waves.py +12 -2
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/deps/deps_file.py +67 -14
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda.py +28 -3
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_base.py +39 -18
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_config.py +1 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_config_defaults.yml +38 -14
- opencos_eda-0.2.52/opencos/eda_deps_sanitize.py +73 -0
- opencos_eda-0.2.52/opencos/tests/test_tools.py +350 -0
- opencos_eda-0.2.52/opencos/tools/cocotb.py +483 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/questa_fse.py +2 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/verilator.py +31 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/yosys.py +21 -5
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/util.py +30 -5
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/utils/markup_helpers.py +31 -2
- opencos_eda-0.2.52/opencos/utils/status_constants.py +27 -0
- opencos_eda-0.2.52/opencos/utils/vscode_helper.py +47 -0
- opencos_eda-0.2.52/opencos/utils/vsim_helper.py +55 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52/opencos_eda.egg-info}/PKG-INFO +2 -1
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos_eda.egg-info/SOURCES.txt +5 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos_eda.egg-info/entry_points.txt +1 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos_eda.egg-info/requires.txt +1 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/pyproject.toml +4 -1
- opencos_eda-0.2.50/opencos/tests/test_tools.py +0 -156
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/LICENSE +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/LICENSE.spdx +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/README.md +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/__init__.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/_version.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/_waves_pkg.sv +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/__init__.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/build.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/elab.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/export.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/flist.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/multi.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/open.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/proj.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/sweep.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/synth.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/targets.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/commands/upload.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/deps/__init__.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/deps/defaults.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/deps/deps_commands.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/deps/deps_processor.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/deps_schema.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_config_max_verilator_waivers.yml +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_config_reduced.yml +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_deps_bash_completion.bash +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_extract_targets.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/eda_tool_helper.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/export_helper.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/export_json_convert.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/files.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/hw/__init__.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/hw/oc_cli.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/hw/pcie.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/names.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/peakrdl_cleanup.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/seed.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/__init__.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/custom_config.yml +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/command_order/DEPS.yml +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/error_msgs/DEPS.yml +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/helpers.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_build.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_deps_helpers.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_deps_schema.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_eda.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_eda_elab.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_eda_synth.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tests/test_oc_cli.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/__init__.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/invio.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/invio_helpers.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/invio_yosys.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/iverilog.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/modelsim_ase.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/questa.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/riviera.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/slang.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/slang_yosys.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/surelog.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/tabbycad_yosys.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/tools/vivado.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/utils/__init__.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/utils/str_helpers.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos/utils/subprocess_helpers.py +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos_eda.egg-info/dependency_links.txt +0 -0
- {opencos_eda-0.2.50 → opencos_eda-0.2.52}/opencos_eda.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
-
|
|
195
|
-
|
|
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(
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
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=
|
|
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)
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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 '
|
|
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 '
|
|
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('#')
|
|
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
|
-
|
|
191
|
+
ret = entry
|
|
185
192
|
|
|
186
193
|
if isinstance(entry, str):
|
|
187
194
|
mylist = dep_str2list(entry) # convert str to list
|
|
188
|
-
|
|
195
|
+
ret = {default_key: mylist}
|
|
189
196
|
|
|
190
197
|
if isinstance(entry, list):
|
|
191
198
|
# it's already a list
|
|
192
|
-
|
|
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
|
-
|
|
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
|
|
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'{
|
|
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)
|
|
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(
|
|
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
|
-
|
|
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([
|