opencos-eda 0.3.11__py3-none-any.whl → 0.3.13__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.
- opencos/commands/flist.py +222 -39
- opencos/commands/multi.py +5 -3
- opencos/commands/sim.py +121 -7
- opencos/commands/sweep.py +1 -1
- opencos/commands/upload.py +4 -4
- opencos/commands/waves.py +44 -0
- opencos/deps/deps_file.py +4 -1
- opencos/deps_schema.py +2 -2
- opencos/eda.py +284 -175
- opencos/eda_base.py +54 -22
- opencos/eda_config.py +51 -24
- opencos/eda_config_defaults.yml +326 -195
- opencos/eda_config_reduced.yml +19 -39
- opencos/eda_tool_helper.py +193 -24
- opencos/tools/cocotb.py +6 -29
- opencos/tools/invio.py +0 -6
- opencos/tools/iverilog.py +16 -16
- opencos/tools/modelsim_ase.py +1 -13
- opencos/tools/quartus.py +81 -24
- opencos/tools/questa.py +0 -14
- opencos/tools/questa_common.py +95 -30
- opencos/tools/questa_fe.py +0 -14
- opencos/tools/questa_fse.py +0 -14
- opencos/tools/riviera.py +56 -25
- opencos/tools/slang.py +15 -12
- opencos/tools/slang_yosys.py +0 -6
- opencos/tools/surelog.py +14 -11
- opencos/tools/tabbycad_yosys.py +1 -7
- opencos/tools/verilator.py +18 -14
- opencos/tools/vivado.py +57 -22
- opencos/tools/yosys.py +2 -4
- opencos/util.py +14 -5
- opencos/utils/str_helpers.py +42 -1
- {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/METADATA +1 -2
- {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/RECORD +40 -41
- opencos/eda_config_max_verilator_waivers.yml +0 -39
- {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/WHEEL +0 -0
- {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/top_level.txt +0 -0
opencos/eda.py
CHANGED
|
@@ -20,6 +20,7 @@ from pathlib import Path
|
|
|
20
20
|
import opencos
|
|
21
21
|
from opencos import util, eda_config, eda_base
|
|
22
22
|
from opencos.eda_base import Command, Tool, which_tool, print_eda_usage_line
|
|
23
|
+
from opencos.eda_tool_helper import pretty_info_handler_tools
|
|
23
24
|
from opencos.files import safe_shutil_which
|
|
24
25
|
from opencos.util import safe_emoji, Colors
|
|
25
26
|
from opencos.utils import vsim_helper, vscode_helper
|
|
@@ -43,18 +44,30 @@ util.global_log.default_log_disable_with_args.extend([
|
|
|
43
44
|
# These are also overriden depending on the tool, for example --tool verilator sets
|
|
44
45
|
# "sim": CommandSimVerilator.
|
|
45
46
|
def init_config(
|
|
46
|
-
config: dict, quiet: bool = False, tool=None, command: str = '',
|
|
47
|
+
config: dict, quiet: bool = False, tool=None, command: str = '', sub_command: str = '',
|
|
47
48
|
run_auto_tool_setup: bool = True
|
|
48
49
|
) -> dict:
|
|
49
|
-
'''Sets or clears entries in config (dict) so tools can be re-loaded.
|
|
50
|
+
'''Sets or clears entries in config (dict) so tools can be re-loaded.
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
- For key DEFAULT_HANDLERS, we'll update config['command_handler'] with
|
|
53
|
+
the actual class using importlib (via opencos.util)
|
|
54
|
+
- 'command', 'sub_command', and 'run_auto_tool_setup' are passed by the config dict,
|
|
55
|
+
the args exist are more for test hooks and debug.
|
|
56
|
+
'''
|
|
53
57
|
|
|
54
58
|
eda_config.update_config_auto_tool_order_for_tool(tool=tool, config=config)
|
|
55
59
|
|
|
60
|
+
if not command and 'command' in config:
|
|
61
|
+
command = config.get('command', '')
|
|
62
|
+
if not sub_command and 'sub_command' in config:
|
|
63
|
+
sub_command = config.get('sub_command', '')
|
|
64
|
+
if 'run_auto_tool_setup' in config:
|
|
65
|
+
run_auto_tool_setup = config.get('run_auto_tool_setup', True)
|
|
66
|
+
|
|
56
67
|
config['command_handler'] = {}
|
|
57
68
|
for _cmd, str_class in config['DEFAULT_HANDLERS'].items():
|
|
69
|
+
if (command or sub_command) and _cmd not in (command, sub_command):
|
|
70
|
+
continue # don't bother importing a default handler class if we won't use it.
|
|
58
71
|
cls = util.import_class_from_string(str_class)
|
|
59
72
|
if not cls:
|
|
60
73
|
util.error(f"config DEFAULT_HANDLERS command={_cmd} {str_class=} could not import")
|
|
@@ -62,9 +75,24 @@ def init_config(
|
|
|
62
75
|
config['command_handler'][_cmd] = cls
|
|
63
76
|
|
|
64
77
|
config['auto_tools_found'] = {}
|
|
65
|
-
config['tools_loaded'] =
|
|
78
|
+
config['tools_loaded'] = []
|
|
79
|
+
|
|
80
|
+
# If this is a sub_command sitation, then use that for setting up tools, otherwise
|
|
81
|
+
# use the command. If there's no command, blank str is fine will load all the tools, or
|
|
82
|
+
# just the specified 'tool'.
|
|
83
|
+
_command_for_tool_setup = command
|
|
84
|
+
if sub_command:
|
|
85
|
+
_command_for_tool_setup = sub_command
|
|
86
|
+
|
|
66
87
|
if run_auto_tool_setup:
|
|
67
|
-
|
|
88
|
+
# Run this with quiet=True, we will print tool/version/path later.
|
|
89
|
+
config = auto_tool_setup(
|
|
90
|
+
config=config, quiet=True, tool=tool, command=_command_for_tool_setup
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if not quiet:
|
|
94
|
+
pretty_info_handler_tools(config=config, command=_command_for_tool_setup)
|
|
95
|
+
|
|
68
96
|
return config
|
|
69
97
|
|
|
70
98
|
|
|
@@ -117,11 +145,11 @@ def usage(tokens: list, config: dict, command: str = "", tool: str = "") -> int:
|
|
|
117
145
|
return 0
|
|
118
146
|
|
|
119
147
|
if command in config['command_handler'].keys():
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if tool and tool !=
|
|
148
|
+
cmd_obj = config['command_handler'][command](config=config) # command object
|
|
149
|
+
cmd_obj_tool = getattr(cmd_obj, '_TOOL', '')
|
|
150
|
+
if tool and tool != cmd_obj_tool:
|
|
123
151
|
util.warning(f'{tool=} does not support {command=}')
|
|
124
|
-
|
|
152
|
+
cmd_obj.help(tokens=tokens)
|
|
125
153
|
return util.exit(0)
|
|
126
154
|
|
|
127
155
|
util.info("Valid commands are:")
|
|
@@ -158,59 +186,75 @@ def interactive(config: dict) -> int:
|
|
|
158
186
|
|
|
159
187
|
|
|
160
188
|
def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-many-statements
|
|
161
|
-
|
|
189
|
+
config = None, quiet: bool = False, tool: str = '',
|
|
162
190
|
command: str = ''
|
|
163
191
|
) -> dict:
|
|
164
|
-
'''Returns an updated config, uses config['auto_tools_order']
|
|
192
|
+
'''Returns an updated config, uses config['auto_tools_order'] dict and
|
|
193
|
+
config['tools'] dict, calls tool_setup(..)
|
|
165
194
|
|
|
166
|
-
-- adds items to config['tools_loaded']
|
|
195
|
+
-- adds items to config['tools_loaded'] list
|
|
167
196
|
-- updates config['command_handler'][command] with a Tool class
|
|
168
197
|
|
|
169
198
|
Input arg tool can be in the form (for example):
|
|
170
199
|
tool='verlator', tool='verilator=/path/to/verilator.exe'
|
|
171
|
-
If so, updates config['
|
|
200
|
+
If so, updates config['tools'][tool]['exe']
|
|
172
201
|
'''
|
|
173
202
|
|
|
174
203
|
tool = eda_config.tool_arg_remove_path_information(tool)
|
|
175
204
|
|
|
176
205
|
assert 'auto_tools_order' in config
|
|
177
|
-
assert isinstance(config['auto_tools_order'],
|
|
178
|
-
assert
|
|
206
|
+
assert isinstance(config['auto_tools_order'], dict)
|
|
207
|
+
assert 'tools' in config
|
|
208
|
+
assert isinstance(config['tools'], dict)
|
|
209
|
+
|
|
210
|
+
if 'auto_tools_found' not in config:
|
|
211
|
+
config['auto_tools_found'] = {}
|
|
212
|
+
if 'tools_loaded' not in config:
|
|
213
|
+
config['tools_loaded'] = []
|
|
214
|
+
|
|
215
|
+
util.debug(f'Calling auto_tool_setup for {tool=} {command=}')
|
|
179
216
|
|
|
180
217
|
if command:
|
|
181
218
|
util.info(f'Auto tool setup for command: {Colors.byellow}{command}')
|
|
182
219
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
220
|
+
# We'd like to achieve two things:
|
|
221
|
+
# 1. Load tool(s) based on args tool, command, config (and what's in config['tools']
|
|
222
|
+
# 2. Select the handling class for a command based on config['auto_tools_order']
|
|
223
|
+
|
|
224
|
+
# Step 1 - Load tools (do not set config['command_handler'][command])
|
|
225
|
+
|
|
226
|
+
if tool:
|
|
227
|
+
# if called with tool=(some_name), then only load that tool (which is not
|
|
228
|
+
# this one). Also delete this entry from config['tools'] because we don't need it.
|
|
229
|
+
for name in list(config['tools'].keys()):
|
|
230
|
+
if tool != name:
|
|
231
|
+
del config['tools'][name]
|
|
232
|
+
|
|
233
|
+
for name, tool_cfg in config['tools'].items():
|
|
234
|
+
|
|
235
|
+
if name in config['auto_tools_found']:
|
|
236
|
+
# we already loaded this tool.
|
|
187
237
|
continue
|
|
188
238
|
|
|
189
|
-
|
|
239
|
+
handlers = config['tools'].get(name, {}).get('handlers', {})
|
|
240
|
+
if command and command not in handlers and \
|
|
190
241
|
command not in config.get('command_has_subcommands', []):
|
|
191
|
-
# Skip tool_setup(..) if the tool handlers can't support command,
|
|
192
|
-
# this is a time-saving feature, but if the comman is multi, tools-multi,
|
|
193
|
-
# sweep, then don't skip this (we don't know what tools we need so load them
|
|
194
|
-
# all.
|
|
195
|
-
# We could figure this out if we went looking for all command(s)
|
|
196
|
-
# multi + sub-command, but that's slightly dangerous if we grab a 'command'
|
|
197
|
-
# from another arg.
|
|
198
242
|
util.debug(f"Skipping tool {name} because it cannot handle {command=}")
|
|
199
243
|
continue
|
|
200
244
|
|
|
201
245
|
util.debug(f"Checking for ability to run tool: {name}")
|
|
202
|
-
|
|
246
|
+
|
|
247
|
+
exe = tool_cfg.get('exe', str())
|
|
203
248
|
if isinstance(exe, list):
|
|
204
249
|
exe_list = exe
|
|
205
250
|
elif isinstance(exe, str):
|
|
206
251
|
exe_list = [exe] # make it a list
|
|
207
252
|
else:
|
|
208
|
-
util.error(f'eda.py: config["
|
|
209
|
-
f'for {exe=}')
|
|
253
|
+
util.error(f'eda.py: config["tools"] for {name=} has bad type for {exe=}')
|
|
210
254
|
continue
|
|
211
255
|
|
|
212
256
|
has_all_py = True
|
|
213
|
-
requires_py_list =
|
|
257
|
+
requires_py_list = tool_cfg.get('requires_py', [])
|
|
214
258
|
for pkg in requires_py_list:
|
|
215
259
|
spec = importlib.util.find_spec(pkg)
|
|
216
260
|
if not spec:
|
|
@@ -218,7 +262,7 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
218
262
|
util.debug(f"... No, missing pkg {spec}")
|
|
219
263
|
|
|
220
264
|
has_all_env = True
|
|
221
|
-
requires_env_list =
|
|
265
|
+
requires_env_list = tool_cfg.get('requires_env', [])
|
|
222
266
|
for env in requires_env_list:
|
|
223
267
|
if not os.environ.get(env, ''):
|
|
224
268
|
has_all_env = False
|
|
@@ -228,20 +272,20 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
228
272
|
has_all_in_exe_path = True
|
|
229
273
|
exe_path = None
|
|
230
274
|
for exe in exe_list:
|
|
231
|
-
assert exe != '', f'{name=}
|
|
275
|
+
assert exe != '', f'{name=} value missing "exe" {exe=}'
|
|
232
276
|
p = safe_shutil_which(exe)
|
|
233
277
|
if not exe_path:
|
|
234
278
|
exe_path = p # set on first required exe
|
|
235
279
|
if not p:
|
|
236
280
|
has_all_exe = False
|
|
237
281
|
util.debug(f"... No, missing exe {exe}")
|
|
238
|
-
for req in
|
|
282
|
+
for req in tool_cfg.get('requires_in_exe_path', []):
|
|
239
283
|
if p and req and str(Path(req)) not in str(Path(p)):
|
|
240
284
|
has_all_in_exe_path = False
|
|
241
285
|
util.debug(f"... No, missing path requirement {req}")
|
|
242
286
|
|
|
243
287
|
has_vsim_helper = True
|
|
244
|
-
if
|
|
288
|
+
if tool_cfg.get('requires_vsim_helper', False):
|
|
245
289
|
# This tool name must be in opencos.utils.vsim_helper.TOOL_PATH[name].
|
|
246
290
|
# Special case for vsim being used by a lot of tools.
|
|
247
291
|
vsim_helper.init() # only runs checks once internally
|
|
@@ -249,7 +293,7 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
249
293
|
has_vsim_helper = bool(exe_path)
|
|
250
294
|
|
|
251
295
|
has_vscode_helper = True
|
|
252
|
-
needs_vscode_extensions =
|
|
296
|
+
needs_vscode_extensions = tool_cfg.get('requires_vscode_extension', None)
|
|
253
297
|
if needs_vscode_extensions:
|
|
254
298
|
if not isinstance(needs_vscode_extensions, list):
|
|
255
299
|
util.error(
|
|
@@ -262,16 +306,15 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
262
306
|
)
|
|
263
307
|
|
|
264
308
|
if has_all_exe:
|
|
265
|
-
requires_cmd_list =
|
|
309
|
+
requires_cmd_list = tool_cfg.get('requires_cmd', [])
|
|
266
310
|
for cmd in requires_cmd_list:
|
|
267
311
|
cmd_list = shlex.split(cmd)
|
|
268
312
|
try:
|
|
269
313
|
proc = subprocess.run(cmd_list, capture_output=True, check=False,
|
|
270
314
|
input=b'exit\n\n')
|
|
271
315
|
if proc.returncode != 0:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
f"({proc.returncode=}): {cmd_list=}")
|
|
316
|
+
util.debug(f"For tool {name} missing required command",
|
|
317
|
+
f"({proc.returncode=}): {cmd_list=}")
|
|
275
318
|
has_all_exe = False
|
|
276
319
|
except Exception as e:
|
|
277
320
|
has_all_exe = False
|
|
@@ -285,97 +328,128 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
285
328
|
else:
|
|
286
329
|
p = safe_shutil_which(exe_list[0])
|
|
287
330
|
config['auto_tools_found'][name] = p # populate key-value pairs w/ first exe in list
|
|
331
|
+
if name not in config['tools_loaded']:
|
|
332
|
+
config['tools_loaded'].append(name)
|
|
288
333
|
if not quiet:
|
|
289
334
|
util.info(f"Detected {name} ({p})")
|
|
290
|
-
tool_setup(
|
|
291
|
-
tool=name, quiet=True, auto_setup=(not tool), warnings=warnings, config=config
|
|
292
|
-
)
|
|
293
335
|
else:
|
|
294
336
|
util.debug(f'Tool {name} is missing one of: {has_all_py=} {has_all_env=}',
|
|
295
337
|
f'{has_all_exe=} {has_all_in_exe_path=} {has_vsim_helper=}',
|
|
296
338
|
f'{has_vscode_helper=}')
|
|
297
339
|
|
|
340
|
+
# Step 2 - set command handlers for command/tool pairs.
|
|
341
|
+
|
|
342
|
+
# If --tool was specified (this function's arg, tool) then we don't need to check
|
|
343
|
+
# config['auto_tools_order'], simply load the handlers this tool supports.
|
|
344
|
+
if tool:
|
|
345
|
+
handlers = config['tools'].get(tool, {}).get('handlers', {})
|
|
346
|
+
if command and command in handlers:
|
|
347
|
+
# --tool and command were set, only load that one handler:
|
|
348
|
+
set_command_handler(
|
|
349
|
+
command=command, tool=tool, config=config, auto_setup=False,
|
|
350
|
+
)
|
|
351
|
+
return config
|
|
352
|
+
|
|
353
|
+
for _command, _ in handlers.items():
|
|
354
|
+
# set all handlers, command was not set or was not in handlers.
|
|
355
|
+
set_command_handler(
|
|
356
|
+
command=_command, tool=tool, config=config, auto_setup=False,
|
|
357
|
+
)
|
|
358
|
+
return config
|
|
359
|
+
|
|
360
|
+
# If --tool was not set, look at config['auto_tools_found'] should be populated based on
|
|
361
|
+
# config['tools'], and if we called this method (auto_tool_setup(...)) with tool= and command=
|
|
362
|
+
# arg(s). Call set_command_handlers for each command/tool we need to:
|
|
363
|
+
for _command, list_of_tools in config['auto_tools_order'].items():
|
|
364
|
+
|
|
365
|
+
for _tool in list_of_tools:
|
|
366
|
+
if _tool not in config['tools_loaded']:
|
|
367
|
+
continue
|
|
368
|
+
|
|
369
|
+
set_command_handler(
|
|
370
|
+
command=_command, tool=_tool, config=config, auto_setup=(not tool),
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
298
374
|
return config
|
|
299
375
|
|
|
300
376
|
|
|
301
|
-
def
|
|
302
|
-
|
|
303
|
-
warnings: bool = True
|
|
377
|
+
def set_command_handler( # pylint: disable=too-many-branches
|
|
378
|
+
command: str, tool: str, config: dict, auto_setup: bool = False
|
|
304
379
|
):
|
|
305
|
-
'''
|
|
306
|
-
|
|
307
|
-
config is potentially updated for entry ['command_handler'][command] with a Tool class.
|
|
380
|
+
'''Updates config['command_handler'] with Command/Tool class
|
|
308
381
|
|
|
309
382
|
Input arg tool can be in the form (for example):
|
|
310
383
|
tool='verlator', tool='verilator=/path/to/verilator.exe'
|
|
311
384
|
|
|
312
385
|
'''
|
|
313
386
|
|
|
314
|
-
tool = eda_config.tool_arg_remove_path_information(tool)
|
|
315
|
-
|
|
316
|
-
if not quiet and not auto_setup:
|
|
317
|
-
util.info(f"Setup for tool: '{tool}'")
|
|
318
387
|
|
|
388
|
+
tool = eda_config.tool_arg_remove_path_information(tool)
|
|
319
389
|
if not tool:
|
|
320
390
|
return
|
|
321
391
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if row:
|
|
331
|
-
util.info(row)
|
|
332
|
-
util.error(f"Don't know how to run tool_setup({tool=}), is not in",
|
|
333
|
-
f"config['auto_tools_order'] from {cfg_yaml_fname}")
|
|
392
|
+
entry = config['tools'][tool]
|
|
393
|
+
tool_cmd_handler_dict = entry.get('handlers', {})
|
|
394
|
+
|
|
395
|
+
current_handler_cls = config['command_handler'].get(command, None)
|
|
396
|
+
|
|
397
|
+
if auto_setup and current_handler_cls is not None and issubclass(current_handler_cls, Tool):
|
|
398
|
+
# If we're not in auto_setup, then always override (aka arg --tool=<this tool>)
|
|
399
|
+
# skip, already has a tool associated with it, and we're in auto_setup=True
|
|
334
400
|
return
|
|
335
401
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
f"{config['auto_tools_found']=} from {cfg_yaml_fname}")
|
|
402
|
+
str_class_name = tool_cmd_handler_dict.get(command, '')
|
|
403
|
+
if not str_class_name:
|
|
404
|
+
# This tool doesn't have a handler for this command
|
|
340
405
|
return
|
|
341
406
|
|
|
342
|
-
|
|
343
|
-
# Do I realy need to warn if a tool was loaded from auto_tool_setup(),
|
|
344
|
-
# but then I also called it via --tool verilator? Only warn if auto_setup=True:
|
|
345
|
-
if warnings:
|
|
346
|
-
util.warning(f"tool_setup: {auto_setup=} already setup for {tool}?")
|
|
407
|
+
cls = util.import_class_from_string(str_class_name)
|
|
347
408
|
|
|
348
|
-
|
|
349
|
-
|
|
409
|
+
if command in config.get('command_determines_tool', []) + \
|
|
410
|
+
config.get('command_tool_is_optional', []):
|
|
411
|
+
# we don't need to confirm the handler parent is a Tool class.
|
|
412
|
+
pass
|
|
413
|
+
else:
|
|
414
|
+
assert issubclass(cls, Tool), \
|
|
415
|
+
f'{str_class_name=} is does not have Tool class associated with it'
|
|
350
416
|
|
|
351
|
-
|
|
352
|
-
|
|
417
|
+
if not auto_setup or \
|
|
418
|
+
command not in config.get('command_determines_tool', []):
|
|
419
|
+
# If not auto_setup - then someone called this --tool by name on the command line,
|
|
420
|
+
# then update the command handler
|
|
421
|
+
# otherwise, if --tool was not set, and command determines tool, leave it with
|
|
422
|
+
# the default handler.
|
|
423
|
+
util.debug(f'Setting {cls=} for {command=} in config.command_handler')
|
|
424
|
+
config['command_handler'][command] = cls
|
|
353
425
|
|
|
354
|
-
if auto_setup and current_handler_cls is not None and issubclass(current_handler_cls, Tool):
|
|
355
|
-
# If we're not in auto_setup, then always override (aka arg --tool=<this tool>)
|
|
356
|
-
# skip, already has a tool associated with it, and we're in auto_setup=True
|
|
357
|
-
continue
|
|
358
426
|
|
|
359
|
-
cls = util.import_class_from_string(str_class_name)
|
|
360
427
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
428
|
+
def get_and_remove_command_from_tokens(
|
|
429
|
+
tokens: list, config: dict, is_sub_command: bool = False
|
|
430
|
+
) -> str:
|
|
431
|
+
'''Attempts to get and remove an eda command (sim, flist, multi, etc) from list
|
|
432
|
+
|
|
433
|
+
- if is_sub_command=True and a sub command was found and not alread set, then add it
|
|
434
|
+
to config['sub_command']
|
|
435
|
+
- if is_sub_command=False, and a command is not already set, then add the found command
|
|
436
|
+
to config['command']
|
|
437
|
+
'''
|
|
368
438
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
config
|
|
439
|
+
command = ''
|
|
440
|
+
for value in tokens:
|
|
441
|
+
if value in config['DEFAULT_HANDLERS'].keys():
|
|
442
|
+
if is_sub_command and value in config['command_has_subcommands']:
|
|
443
|
+
continue # only 1 sub command allowed, not a chain.
|
|
444
|
+
command = value
|
|
445
|
+
tokens.remove(value) # remove command (flist, export, targets, etc)
|
|
446
|
+
if not is_sub_command and not config.get('command', ''):
|
|
447
|
+
config['command'] = value
|
|
448
|
+
if is_sub_command and not config.get('sub_command', ''):
|
|
449
|
+
config['sub_command'] = value
|
|
450
|
+
break
|
|
377
451
|
|
|
378
|
-
|
|
452
|
+
return command
|
|
379
453
|
|
|
380
454
|
|
|
381
455
|
def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-many-locals,too-many-return-statements
|
|
@@ -395,7 +469,7 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
395
469
|
|
|
396
470
|
deferred_tokens = []
|
|
397
471
|
command = ""
|
|
398
|
-
|
|
472
|
+
sub_command = ""
|
|
399
473
|
|
|
400
474
|
parser = eda_base.get_argparser()
|
|
401
475
|
try:
|
|
@@ -420,28 +494,31 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
420
494
|
|
|
421
495
|
util.debug(f'eda process_tokens: {parsed=} {unparsed=}')
|
|
422
496
|
|
|
423
|
-
# Attempt to get the 'command'
|
|
424
|
-
#
|
|
425
|
-
#
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
497
|
+
# Attempt to get the 'command' and optional sub-command. It is removed
|
|
498
|
+
# from unparsed if found, and if a sub_command is found it is set in
|
|
499
|
+
# config['sub_command']
|
|
500
|
+
command = get_and_remove_command_from_tokens(
|
|
501
|
+
tokens=unparsed, config=config, is_sub_command=False
|
|
502
|
+
)
|
|
503
|
+
if command and command in config.get('command_has_subcommands', []):
|
|
504
|
+
sub_command = get_and_remove_command_from_tokens(
|
|
505
|
+
tokens=unparsed, config=config, is_sub_command=True
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
if not parsed.tool:
|
|
509
|
+
if not sub_command and command and command in config['command_tool_is_optional']:
|
|
510
|
+
# --tool not set, and tool was optional for primary command
|
|
511
|
+
config['run_auto_tool_setup'] = False
|
|
512
|
+
if sub_command and sub_command in config['command_tool_is_optional']:
|
|
513
|
+
# --tool not set, and tool was optional for secondary command
|
|
514
|
+
config['run_auto_tool_setup'] = False
|
|
435
515
|
|
|
436
516
|
if not is_interactive:
|
|
437
517
|
# Run init_config() now, we deferred it in main(), but only run it
|
|
438
518
|
# for this tool (or tool=None to figure it out)
|
|
439
519
|
# This will handle any --tool=<name>=/path/to/bin also, so don't have to
|
|
440
520
|
# run tool_setup(..) on its own.
|
|
441
|
-
config = init_config(
|
|
442
|
-
config, tool=parsed.tool, command=command,
|
|
443
|
-
run_auto_tool_setup=run_auto_tool_setup
|
|
444
|
-
)
|
|
521
|
+
config = init_config(config, tool=parsed.tool)
|
|
445
522
|
if not config:
|
|
446
523
|
util.error(f'eda.py main: problem loading config, {tokens=}')
|
|
447
524
|
return 3
|
|
@@ -463,69 +540,81 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
463
540
|
"valid commands)!")
|
|
464
541
|
return 2
|
|
465
542
|
|
|
466
|
-
|
|
543
|
+
cmd_obj = config['command_handler'][command](config=config) # command object
|
|
467
544
|
if not parsed.tool:
|
|
468
545
|
# then what was the auto selected tool?
|
|
469
|
-
|
|
470
|
-
if
|
|
471
|
-
config['
|
|
472
|
-
util.error(f'Cannot use tool={
|
|
546
|
+
cmd_obj_tool = getattr(cmd_obj, '_TOOL', '')
|
|
547
|
+
if cmd_obj_tool in config['auto_tools_found'] and \
|
|
548
|
+
config['tools'].get(cmd_obj_tool, {}).get('disable-auto', False):
|
|
549
|
+
util.error(f'Cannot use tool={cmd_obj_tool} without arg --tool, it cannot be selected',
|
|
473
550
|
'automatically')
|
|
474
551
|
return status_constants.EDA_COMMAND_OR_ARGS_ERROR
|
|
475
552
|
|
|
476
|
-
util.debug(f'{command=}')
|
|
477
|
-
util.debug(f'{
|
|
478
|
-
util.debug(f'{type(
|
|
553
|
+
util.debug(f'{command=} {sub_command=}')
|
|
554
|
+
util.debug(f'{cmd_obj.config=}')
|
|
555
|
+
util.debug(f'{type(cmd_obj)=}')
|
|
479
556
|
if not parsed.tool and \
|
|
480
557
|
command not in config.get('command_determines_tool', []) and \
|
|
481
558
|
command not in config.get('command_tool_is_optional', []):
|
|
559
|
+
# --tool not set, tool was not optional, so we have to use the tool from the handler cmd_obj
|
|
560
|
+
# (our best guess at tool to use)
|
|
482
561
|
use_tool = which_tool(command, config)
|
|
483
562
|
if use_tool:
|
|
484
|
-
util.info(f"--tool not specified, using default for {command=}:
|
|
563
|
+
util.info(f"--tool not specified, using default for {command=}:",
|
|
564
|
+
f"{Colors.bgreen}{use_tool}")
|
|
485
565
|
else:
|
|
486
566
|
# Not all commands have a hard requirement on tool (such as 'multi') because we
|
|
487
567
|
# haven't examined sub-commands yet.
|
|
488
|
-
util.info(
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
568
|
+
util.info('--tool not specified, will attempt to determine tool(s) for',
|
|
569
|
+
f'{Colors.bgreen}{command=}.')
|
|
570
|
+
setattr(cmd_obj, 'auto_tool_applied', True)
|
|
571
|
+
|
|
572
|
+
# For command (and optional sub_command) make sure our handler class is checked to run,
|
|
573
|
+
# (does it require a Tool class, etc)
|
|
574
|
+
rc = check_command_handler_cls(command_obj=cmd_obj, command=command, parsed_args=parsed)
|
|
575
|
+
if sub_command:
|
|
576
|
+
sub_cmd_obj = config['command_handler'][sub_command](config=config) # command object
|
|
577
|
+
rc |= check_command_handler_cls(
|
|
578
|
+
command_obj=sub_cmd_obj, command=sub_command, parsed_args=parsed
|
|
579
|
+
)
|
|
492
580
|
if rc > 0:
|
|
493
|
-
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(
|
|
581
|
+
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(cmd_obj)=},'
|
|
494
582
|
f'unparsed={deferred_tokens}')
|
|
495
583
|
return rc
|
|
496
584
|
|
|
497
585
|
# Add the original, nothing-parsed args to the Command.config dict.
|
|
498
|
-
|
|
586
|
+
cmd_obj.config['eda_original_args'] = original_args
|
|
499
587
|
|
|
500
|
-
|
|
501
|
-
|
|
588
|
+
# as a safeguard, the command_name is not always passed to 'cmd_obj', so let's do that now.
|
|
589
|
+
setattr(cmd_obj, 'command_name', command)
|
|
590
|
+
unparsed = cmd_obj.process_tokens(tokens=deferred_tokens, pwd=os.getcwd())
|
|
502
591
|
|
|
503
592
|
# query the status from the Command object (0 is pass, > 0 is fail, but we'd prefer to avoid
|
|
504
593
|
# rc=1 because that's the python exception rc)
|
|
505
|
-
rc = getattr(
|
|
506
|
-
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(
|
|
594
|
+
rc = getattr(cmd_obj, 'status', 2)
|
|
595
|
+
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(cmd_obj)=}, {unparsed=}')
|
|
507
596
|
|
|
508
|
-
if rc == 0 and not parsed.tool and getattr(
|
|
509
|
-
return
|
|
510
|
-
|
|
597
|
+
if rc == 0 and not parsed.tool and getattr(cmd_obj, 'tool_changed_respawn', False):
|
|
598
|
+
return respawn_new_command_object(
|
|
599
|
+
cmd_obj=cmd_obj, parsed=parsed, config=config, command=command, tokens=tokens,
|
|
511
600
|
deferred_tokens=deferred_tokens
|
|
512
601
|
)
|
|
513
602
|
|
|
514
603
|
return rc
|
|
515
604
|
|
|
516
605
|
|
|
517
|
-
def
|
|
518
|
-
|
|
606
|
+
def respawn_new_command_object(
|
|
607
|
+
cmd_obj: Command, parsed: argparse.Namespace, config: dict, command: str,
|
|
519
608
|
tokens: list, deferred_tokens: list
|
|
520
609
|
) -> int:
|
|
521
610
|
'''Returns retcode (int). Creates a new Command object, presumably using a different tool,
|
|
522
611
|
|
|
523
612
|
due to args changes from DEPS parsing that led to --tool=<different value> vs the automatic
|
|
524
|
-
value if --tool was not originally set. Will run process_tokens(..) on the new
|
|
613
|
+
value if --tool was not originally set. Will run process_tokens(..) on the new commmand
|
|
525
614
|
object.
|
|
526
615
|
'''
|
|
527
616
|
|
|
528
|
-
use_tool =
|
|
617
|
+
use_tool = cmd_obj.args.get('tool', '')
|
|
529
618
|
|
|
530
619
|
if not use_tool:
|
|
531
620
|
util.error(f'Unable to change tool from {parsed.tool}, internal eda problem.')
|
|
@@ -536,8 +625,8 @@ def respawn_new_sub_command_object(
|
|
|
536
625
|
f'{Colors.byellow}{command}')
|
|
537
626
|
|
|
538
627
|
# Update the command handler(s) with this new tool. We don't really respawn, just
|
|
539
|
-
# try to swap out the
|
|
540
|
-
entry = config['
|
|
628
|
+
# try to swap out the cmd_obj (Command obj handle)
|
|
629
|
+
entry = config['tools'].get(use_tool, {})
|
|
541
630
|
tool_cmd_handler_dict = entry.get('handlers', {})
|
|
542
631
|
|
|
543
632
|
for _command, str_class_name in tool_cmd_handler_dict.items():
|
|
@@ -558,64 +647,81 @@ def respawn_new_sub_command_object(
|
|
|
558
647
|
util.debug(f'Setting {cls=} for command={_command} in config.command_handler')
|
|
559
648
|
config['command_handler'][_command] = cls
|
|
560
649
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
util.debug(f'No longer using handler: {type(
|
|
564
|
-
|
|
565
|
-
del
|
|
650
|
+
old_cmd_obj = cmd_obj
|
|
651
|
+
cmd_obj = config['command_handler'][command](config=config) # command object
|
|
652
|
+
util.debug(f'No longer using handler: {type(old_cmd_obj)}; now using: {type(cmd_obj)}')
|
|
653
|
+
cmd_obj.config['eda_original_args'] = old_cmd_obj.config['eda_original_args']
|
|
654
|
+
del old_cmd_obj
|
|
566
655
|
|
|
567
|
-
rc = check_command_handler_cls(command_obj=
|
|
656
|
+
rc = check_command_handler_cls(command_obj=cmd_obj, command=command, parsed_args=parsed)
|
|
568
657
|
if rc > 0:
|
|
569
|
-
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(
|
|
658
|
+
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(cmd_obj)=},'
|
|
570
659
|
f'unparsed={deferred_tokens}')
|
|
571
660
|
return rc
|
|
572
661
|
|
|
573
|
-
setattr(
|
|
662
|
+
setattr(cmd_obj, 'command_name', command) # as a safeguard, 'command' set in 'cmd_obj'
|
|
574
663
|
util.info(f'--tool={use_tool}: running command: {Colors.byellow}eda {command} ',
|
|
575
664
|
' '.join(deferred_tokens))
|
|
576
|
-
unparsed =
|
|
665
|
+
unparsed = cmd_obj.process_tokens(tokens=deferred_tokens, pwd=os.getcwd())
|
|
577
666
|
|
|
578
667
|
# query the status from the Command object (0 is pass, > 0 is fail, but we'd prefer to
|
|
579
668
|
# avoid rc=1 because that's the python exception rc)
|
|
580
|
-
rc = getattr(
|
|
581
|
-
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(
|
|
669
|
+
rc = getattr(cmd_obj, 'status', 2)
|
|
670
|
+
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(cmd_obj)=}, {unparsed=}')
|
|
582
671
|
return rc
|
|
583
672
|
|
|
584
673
|
|
|
585
|
-
def check_command_handler_cls(command_obj:object, command:str, parsed_args) -> int:
|
|
674
|
+
def check_command_handler_cls(command_obj: object, command: str, parsed_args) -> int:
|
|
586
675
|
'''Returns bash/sh return code, checks that a command handling class has all
|
|
587
676
|
|
|
588
677
|
internal CHECK_REQUIRES list items. For example, sim.py has CHECK_REQUIRES=[Tool],
|
|
589
678
|
so if a 'sim' command handler does not also inherit a Tool class, then reports this as an
|
|
590
679
|
error.
|
|
591
680
|
'''
|
|
592
|
-
|
|
593
|
-
for cls in getattr(
|
|
594
|
-
if not isinstance(
|
|
595
|
-
# If someone set --tool verilator for command=synth, then our '
|
|
596
|
-
# to CommandSynth with no tool attached. If we don't have a tool set,
|
|
681
|
+
cmd_obj = command_obj
|
|
682
|
+
for cls in getattr(cmd_obj, 'CHECK_REQUIRES', []):
|
|
683
|
+
if not isinstance(cmd_obj, cls):
|
|
684
|
+
# If someone set --tool verilator for command=synth, then our 'cmd_obj' will have
|
|
685
|
+
# defaulted to CommandSynth with no tool attached. If we don't have a tool set, and one
|
|
686
|
+
# is requires, error and return.
|
|
597
687
|
parsed_tool = getattr(parsed_args, 'tool', '')
|
|
598
|
-
|
|
599
|
-
'
|
|
600
|
-
if parsed_tool and not
|
|
688
|
+
tool_entry = command_obj.config.get(
|
|
689
|
+
'tools', {}).get(parsed_tool, {})
|
|
690
|
+
if parsed_tool and not tool_entry:
|
|
601
691
|
util.warning(
|
|
602
|
-
f"{command=} for tool '{parsed_tool}' is using handling class
|
|
603
|
-
f"but missing requirement {cls}, likely because the tool
|
|
604
|
-
"(not in PATH) or mis-configured (such as missing a Tool based
|
|
692
|
+
f"{command=} for tool '{parsed_tool}' is using handling class",
|
|
693
|
+
f"'{type(cmd_obj)}', but missing requirement {cls}, likely because the tool",
|
|
694
|
+
"was not loaded (not in PATH) or mis-configured (such as missing a Tool based",
|
|
695
|
+
"class)"
|
|
605
696
|
)
|
|
606
697
|
return util.error(
|
|
607
698
|
f"EDA {command=} for tool '{parsed_tool}' cannot be run because tool",
|
|
608
699
|
f"'{parsed_tool}' is not known to `eda`. It does not exist in the config:",
|
|
609
700
|
"see informational message for --config-yml, and check that file's",
|
|
610
|
-
"
|
|
701
|
+
"tools config."
|
|
611
702
|
)
|
|
612
703
|
if parsed_tool:
|
|
704
|
+
# Does the tool_entry even have handlers for this command?
|
|
705
|
+
if command not in tool_entry.get('handlers', {}):
|
|
706
|
+
util.warning(
|
|
707
|
+
f"{command=} for tool '{parsed_tool}' does not have a handler defined in",
|
|
708
|
+
"the config: see informational message for --config-yml, and check that",
|
|
709
|
+
f"file's tools config. Known handlers for {parsed_tool} are: ",
|
|
710
|
+
f"{tool_entry.get('handlers', '')}"
|
|
711
|
+
)
|
|
712
|
+
return util.error(
|
|
713
|
+
f"{command=} for tool '{parsed_tool}' does not have a handler defined in",
|
|
714
|
+
"the config"
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
# It does have a handler defined, maybe it wasn't loaded due to other reasons?
|
|
613
718
|
util.warning(
|
|
614
|
-
f"{command=} for tool '{parsed_tool}' is using handling class
|
|
615
|
-
f"but missing requirement {cls}, likely because the tool
|
|
616
|
-
"(not in PATH) or mis-configured (such as missing a Tool based
|
|
719
|
+
f"{command=} for tool '{parsed_tool}' is using handling class",
|
|
720
|
+
f"'{type(cmd_obj)}', but missing requirement {cls}, likely because the tool",
|
|
721
|
+
"was not loaded (not in PATH) or mis-configured (such as missing a Tool based",
|
|
722
|
+
"class)"
|
|
617
723
|
)
|
|
618
|
-
for k,v in
|
|
724
|
+
for k,v in tool_entry.items():
|
|
619
725
|
if k == 'exe' or k.startswith('requires_cmd'):
|
|
620
726
|
util.warning(
|
|
621
727
|
f"tool '{parsed_tool}' has requirements that may not have been met --",
|
|
@@ -638,8 +744,8 @@ def check_command_handler_cls(command_obj:object, command:str, parsed_args) -> i
|
|
|
638
744
|
# No parsed_tool.
|
|
639
745
|
util.warning(
|
|
640
746
|
f"{command=} for default tool (--tool not set) is using handling class",
|
|
641
|
-
f"'{type(
|
|
642
|
-
"loaded (not in PATH) or mis-configured (such as missing a Tool based class)"
|
|
747
|
+
f"'{type(cmd_obj)}', but missing requirement {cls}, likely because the tool was",
|
|
748
|
+
"not loaded (not in PATH) or mis-configured (such as missing a Tool based class)"
|
|
643
749
|
)
|
|
644
750
|
return util.error(
|
|
645
751
|
f"EDA {command=} for default tool (--tool not set) is not supported (default",
|
|
@@ -692,6 +798,9 @@ def main(*args):
|
|
|
692
798
|
# And show the command that was run (all args):
|
|
693
799
|
util.info(f"main: {Colors.byellow}eda {' '.join(args)}{Colors.normal}{Colors.green};",
|
|
694
800
|
f"(run from {os.getcwd()})")
|
|
801
|
+
# And show python version:
|
|
802
|
+
util.info(f'python: version {sys.version_info.major}.{sys.version_info.minor}.'
|
|
803
|
+
f'{sys.version_info.micro}')
|
|
695
804
|
|
|
696
805
|
# Handle --config-yml= arg
|
|
697
806
|
config, unparsed = eda_config.get_eda_config(unparsed)
|