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