opencos-eda 0.3.8__py3-none-any.whl → 0.3.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. opencos/commands/deps_help.py +40 -21
  2. opencos/commands/sim.py +0 -1
  3. opencos/deps/deps_file.py +82 -79
  4. opencos/eda.py +108 -17
  5. opencos/eda_base.py +8 -4
  6. opencos/eda_config.py +8 -1
  7. opencos/eda_config_defaults.yml +14 -5
  8. opencos/eda_deps_bash_completion.bash +37 -15
  9. opencos/tools/modelsim_ase.py +19 -378
  10. opencos/tools/questa.py +42 -247
  11. opencos/tools/questa_common.py +480 -0
  12. opencos/tools/questa_fe.py +84 -0
  13. opencos/tools/questa_fse.py +7 -8
  14. opencos/tools/riviera.py +27 -10
  15. opencos/tools/verilator.py +1 -0
  16. opencos/utils/str_helpers.py +7 -0
  17. opencos/utils/vsim_helper.py +53 -21
  18. {opencos_eda-0.3.8.dist-info → opencos_eda-0.3.10.dist-info}/METADATA +2 -1
  19. {opencos_eda-0.3.8.dist-info → opencos_eda-0.3.10.dist-info}/RECORD +24 -40
  20. {opencos_eda-0.3.8.dist-info → opencos_eda-0.3.10.dist-info}/entry_points.txt +1 -0
  21. opencos/tests/__init__.py +0 -0
  22. opencos/tests/custom_config.yml +0 -13
  23. opencos/tests/deps_files/command_order/DEPS.yml +0 -44
  24. opencos/tests/deps_files/error_msgs/DEPS.yml +0 -55
  25. opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -4
  26. opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
  27. opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -50
  28. opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -54
  29. opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -4
  30. opencos/tests/helpers.py +0 -354
  31. opencos/tests/test_build.py +0 -12
  32. opencos/tests/test_deps_helpers.py +0 -207
  33. opencos/tests/test_deps_schema.py +0 -30
  34. opencos/tests/test_eda.py +0 -921
  35. opencos/tests/test_eda_elab.py +0 -110
  36. opencos/tests/test_eda_synth.py +0 -150
  37. opencos/tests/test_oc_cli.py +0 -25
  38. opencos/tests/test_tools.py +0 -404
  39. {opencos_eda-0.3.8.dist-info → opencos_eda-0.3.10.dist-info}/WHEEL +0 -0
  40. {opencos_eda-0.3.8.dist-info → opencos_eda-0.3.10.dist-info}/licenses/LICENSE +0 -0
  41. {opencos_eda-0.3.8.dist-info → opencos_eda-0.3.10.dist-info}/licenses/LICENSE.spdx +0 -0
  42. {opencos_eda-0.3.8.dist-info → opencos_eda-0.3.10.dist-info}/top_level.txt +0 -0
@@ -10,32 +10,37 @@ it is generally run as simply
10
10
  uses no tools and will print a help text regarding DEPS markup files to stdout.
11
11
  '''
12
12
 
13
+ # pylint: disable=line-too-long
13
14
 
14
15
  import os
15
16
 
16
17
  from opencos.eda_base import Command
17
18
  from opencos import util
19
+ from opencos.util import Colors
18
20
 
19
21
 
20
- BASIC_DEPS_HELP = '''
22
+ BASIC_DEPS_HELP = f'''
23
+ {Colors.yellow}
24
+ Note: you can run with one of: {Colors.cyan}--verbose, --help, --debug{Colors.yellow} to show full
25
+ schema supported, or {Colors.cyan}--no-color{Colors.yellow} to avoid printing this text with colors.
21
26
 
22
- --------------------------------------------------------------------
27
+ {Colors.green}--------------------------------------------------------------------{Colors.yellow}
23
28
 
24
- What is a DEPS.yml file and why does `eda` use this?
25
- - DEPS.yml is a fancy filelist.
29
+ What is a {Colors.byellow}DEPS.yml{Colors.normal}{Colors.yellow} file and why does `eda` use this?
30
+ - {Colors.byellow}DEPS.yml{Colors.normal}{Colors.yellow} is a fancy filelist.
26
31
  - Used to organize a project into "targets", a tool can run on a "target".
27
32
  - Allows for more than just source files attached to a "target".
28
33
  -- incdirs, defines, and args can be applied to a "target".
29
34
 
30
- --------------------------------------------------------------------
35
+ {Colors.green}--------------------------------------------------------------------{Colors.yellow}
31
36
 
32
37
  Hello World example:
33
38
 
34
- The following example is a DEPS.yml file example for a SystemVerilog simulation of
35
- hello_world_tb.sv. DEPS.yml is, in short, a fancy filelist. We use them in the `eda`
39
+ The following example is a {Colors.byellow}DEPS.yml{Colors.normal}{Colors.yellow} file example for a SystemVerilog simulation of
40
+ hello_world_tb.sv. {Colors.byellow}DEPS.yml{Colors.normal}{Colors.yellow} is, in short, a fancy filelist. We use them in the `eda`
36
41
  app to organize projects.
37
42
 
38
- --- DEPS.yml: ---
43
+ --- {Colors.byellow}DEPS.yml{Colors.normal}{Colors.yellow}: ---
39
44
 
40
45
  hello-world: # <-- this is a named target that will be run
41
46
 
@@ -64,20 +69,20 @@ endmodule : hello_world_tb
64
69
 
65
70
 
66
71
  hello-world:
67
- The target name in the DEPS.yml we named is hello-world. That is a valid target
72
+ The target name in the {Colors.byellow}DEPS.yml{Colors.normal}{Colors.yellow} we named is hello-world. That is a valid target
68
73
  that `eda` can use. Such as:
69
74
 
70
75
  eda sim --tool=verilator hello-world
71
76
 
72
77
 
73
- --------------------------------------------------------------------
78
+ {Colors.green}--------------------------------------------------------------------{Colors.yellow}
74
79
 
75
80
  Beyond Hello World example:
76
81
 
77
82
  The following example is a DEPS.yml file for a more complex module simulation.
78
83
  It has two files in ./DEPS.yml and ./lib/DEPS.yml.
79
84
 
80
- --- ./DEPS.yml: ---
85
+ --- {Colors.byellow}./DEPS.yml{Colors.normal}{Colors.yellow}: ---
81
86
 
82
87
  my_fifo: # <-- this is a design
83
88
  incdirs: . lib # <-- 'incdirs' define the paths searched to find `include files
@@ -102,7 +107,7 @@ my_fifo_stress_test: # <-- this is another TEST
102
107
  deps:
103
108
  - my_fifo_test # aside from the define, this is same as "my_fifo_test"
104
109
 
105
- --- lib/DEPS.yml: ---
110
+ --- {Colors.byellow}lib/DEPS.yml{Colors.normal}{Colors.yellow}: ---
106
111
 
107
112
  lib_pkg: # <-- this is a package required by bin_to_gray below
108
113
  deps:
@@ -115,15 +120,15 @@ bin_to_gray: # <-- this is the target that was required by .
115
120
  # to be read before the code that uses them
116
121
  - bin_to_gray.sv # an SV module pulled in directly
117
122
 
118
- --------------------------------------------------------------------
123
+ {Colors.green}--------------------------------------------------------------------{Colors.yellow}
119
124
  '''
120
125
 
121
126
 
122
- FULL_DEPS_HELP = '''
127
+ FULL_DEPS_HELP = f'''
123
128
 
124
- --------------------------------------------------------------------
129
+ {Colors.green}--------------------------------------------------------------------{Colors.yellow}
125
130
 
126
- Full DEPS.yml schema:
131
+ Full {Colors.byellow}DEPS.yml{Colors.normal}{Colors.yellow} schema:
127
132
 
128
133
  ```
129
134
  DEFAULTS: # <table> defaults applied to ALL targets in this file, local targets ** override ** the defaults.
@@ -140,6 +145,14 @@ target-spec:
140
145
  SOME_DEFINE: value
141
146
  SOME_DEFINE_NO_VALUE: # we just leave this blank, or use nil (yaml's None)
142
147
 
148
+ plusargs: # <table>
149
+ variable0: value
150
+ variable1: # blank for no value, or use nil (yaml's None)
151
+
152
+ parameters: # <table>
153
+ SomeParameter: value
154
+ SOME_OTHER_PARAMETER: value
155
+
143
156
  incdirs: # <array>
144
157
  - some/relative/path
145
158
 
@@ -150,20 +163,22 @@ target-spec:
150
163
  - some_file.sv # <string> aka, a file
151
164
  - sv@some_file.txt # <string> aka, ext@file where we'd like a file not ending in .sv to be
152
165
  # treated as a .sv file for tools.
153
- # Supported for sv@, v@, vhdl@, cpp@
166
+ # Supported for sv@, v@, vhdl@, cpp@, sdc@, f@, py@, makefile@
154
167
  - commands: # <table> with key 'commands' for a <array>: support for built-in commands
155
168
  # Note this cannot be confused for other targets or files.
156
169
  - shell: # <string>
157
- var-subst-args: # <bool> default false. If true, substitute vars in commands, such as {fpga}
158
- # substituted from eda arg --fpga=SomeFpga, such that {fpga} becomes SomeFpga
170
+ var-subst-args: # <bool> default false. If true, substitute vars in commands, such as {{fpga}}
171
+ # substituted from eda arg --fpga=SomeFpga, such that {{fpga}} becomes SomeFpga
159
172
  var-subst-os-env: #<bool> default false. If true, substitute vars in commands using os.environ vars,
160
- # such as {FPGA} could get substituted by env value for $FPGA
161
- tee: # <string> optional filename, otherwise shell commands write to {target-spec}__shell_0.log
173
+ # such as {{FPGA}} could get substituted by env value for $FPGA
174
+ tee: # <string> optional filename, otherwise shell commands write to {{target-spec}}__shell_0.log
162
175
  run-from-work-dir: #<bool> default true. If false, runs from the directory of this DEPS file.
163
176
  filepath-subst-target-dir: #<bool> default true. If false, disables shell file path
164
177
  substituion on this target's directory (this DEPS file dir).
165
178
  dirpath-subst-target-dir: #<bool> default false. If true, enables shell directory path
166
179
  substituion on this target's directory (this DEPS file dir).
180
+ run-after-tool: # <bool> default false. Set to true to run after any EDA tools, or
181
+ any command handlers have completed.
167
182
  - shell: echo "Hello World!"
168
183
  - work-dir-add-sources: # <array or | space separated string>, this is how to add generated files
169
184
  # to compile order list.
@@ -196,6 +211,8 @@ target-spec:
196
211
  args: # <array or | space separated string>
197
212
  deps: # <array or | space separated string> # Note: not implemented yet
198
213
  defines: ## <table>
214
+ plusargs: ## <table>
215
+ parameters: ## <table>
199
216
  incdirs: ## <array>
200
217
 
201
218
  tags: # <table> this is the currently support tags features in a target.
@@ -215,6 +232,8 @@ target-spec:
215
232
  # tool in 'with-tools'.
216
233
  deps: <array or | space separated string, applied with tag>
217
234
  defines: <table, applied with tag>
235
+ plusargs: <table, applied with tag>
236
+ parameters: <table, applied with tag>
218
237
  incdirs: <array, applied with tag>
219
238
  replace-config-tools: <table> # spec matching eda_config_defaults.yml::tools.<tool> (replace merge strategy)
220
239
  additive-config-tools: <table> # spec matching eda_config_defaults.yml::tools.<tool> (additive merge strategy)
opencos/commands/sim.py CHANGED
@@ -300,7 +300,6 @@ class CommandSim(CommandDesign): # pylint: disable=too-many-public-methods
300
300
  tool = self.args.get('tool', None)
301
301
  # Certain args are allow-listed here
302
302
  deps_file_args = []
303
- print(f'SUPER DREW DEBUG: {self.get_command_line_args()=}')
304
303
  for a in self.get_command_line_args():
305
304
  if any(a.startswith(x) for x in [
306
305
  '--compile-args',
opencos/deps/deps_file.py CHANGED
@@ -16,7 +16,7 @@ from opencos.util import debug, error
16
16
  from opencos.utils.markup_helpers import yaml_safe_load, toml_load_only_root_line_numbers, \
17
17
  markup_writer, markup_dumper
18
18
  from opencos.utils.str_helpers import fnmatch_or_re, dep_str2list, pretty_list_columns_manual, \
19
- is_valid_target_name, VALID_TARGET_INFO_STR
19
+ is_valid_target_name, VALID_TARGET_INFO_STR, get_shorter_path_str_rel_vs_abs
20
20
  from opencos.utils.subprocess_helpers import subprocess_run_background
21
21
  from opencos.utils.status_constants import EDA_DEPS_FILE_NOT_FOUND, EDA_DEPS_TARGET_NOT_FOUND
22
22
 
@@ -270,9 +270,12 @@ class DepsFile:
270
270
  return f'line={self.line_numbers.get(target_node, "")}'
271
271
 
272
272
  def gen_caller_info(self, target: str) -> str:
273
- '''Given a full target name (path/to/my_target) return caller_info str for debug'''
273
+ '''Given a full target name (path/to/my_target) return caller_info str for debug
274
+
275
+ Use abspath if the str is shorter, for the path information part.
276
+ '''
274
277
  return '::'.join([
275
- self.rel_deps_file,
278
+ get_shorter_path_str_rel_vs_abs(rel_path=self.rel_deps_file),
276
279
  target,
277
280
  self.get_approx_line_number_str(target)
278
281
  ])
@@ -298,90 +301,90 @@ class DepsFile:
298
301
  some caller_info(str). This is more useful for YAML or TOML markup where we have
299
302
  caller_info.
300
303
  '''
301
- if target_node not in self.data:
302
- found_target = False
303
304
 
304
- if target_node.startswith('-'):
305
- # likely an unparsed arg that made it this far.
306
- util.warning(f"Ignoring unparsed argument '{target_node}'")
307
- return False
305
+ if target_node in self.data:
306
+ debug(f'Found {target_node=} in deps_file={self.rel_deps_file}')
307
+ return True
308
308
 
309
- # For error printing, prefer relative paths:
310
- if self.target_path:
311
- t_path = os.path.relpath(self.target_path) + os.path.sep
312
- else:
313
- t_path = ''
314
- t_node = target_node
315
- t_full = os.path.join(t_path, t_node)
309
+ if target_node.startswith('-'):
310
+ # likely an unparsed arg that made it this far.
311
+ util.warning(f"Ignoring unparsed argument '{target_node}'")
312
+ return False
316
313
 
317
- if not is_valid_target_name(target_node):
318
- util.warning(
319
- f"In file {self.rel_deps_file}, {target_node} {VALID_TARGET_INFO_STR}"
320
- )
314
+ # For error printing, prefer relative paths, unless the abspath is shorter:
315
+ if self.target_path:
316
+ t_path = os.path.relpath(self.target_path) + os.path.sep
317
+ t_path = get_shorter_path_str_rel_vs_abs(rel_path=t_path)
318
+ else:
319
+ t_path = ''
320
+ t_node = target_node
321
+ t_full = os.path.join(t_path, t_node)
321
322
 
322
- if not caller_info:
323
- # If we don't have caller_info, likely came from command line (or DEPS JSON data):
324
- if '.' in target_node:
325
- # Likely a filename (target_node does not include path)
326
- self.error_ifarg(
327
- f'Trying to resolve command-line target={t_full} (file?):',
328
- f'File={t_node} not found in directory={t_path}',
329
- arg='error-unknown-args',
330
- error_code=EDA_DEPS_FILE_NOT_FOUND
331
- )
332
- elif not self.rel_deps_file:
333
- # target, but there's no DEPS file
334
- self.error_ifarg(
335
- f'Trying to resolve command-line target={t_full}:',
336
- f'but path {t_path} has no DEPS markup file (DEPS.yml)',
337
- arg='error-unknown-args',
338
- error_code=EDA_DEPS_FILE_NOT_FOUND
339
- )
340
- else:
341
- self.warning_show_available_targets()
342
- self.error_ifarg(
343
- f'Trying to resolve command-line target={t_full}:',
344
- f'was not found in deps_file={self.rel_deps_file}',
345
- arg='error-unknown-args',
346
- error_code=EDA_DEPS_TARGET_NOT_FOUND
347
- )
323
+ if not is_valid_target_name(target_node):
324
+ util.warning(
325
+ f"In file {self.rel_deps_file}, {target_node} {VALID_TARGET_INFO_STR}"
326
+ )
348
327
 
328
+ if not caller_info:
329
+ # If we don't have caller_info, likely came from command line (or DEPS JSON data):
330
+ if '.' in target_node:
331
+ # Likely a filename (target_node does not include path)
332
+ self.error_ifarg(
333
+ f'Trying to resolve command-line target={t_full} (file?):',
334
+ f'File={t_node} not found in directory={t_path}',
335
+ arg='error-unknown-args',
336
+ error_code=EDA_DEPS_FILE_NOT_FOUND
337
+ )
338
+ elif not self.rel_deps_file:
339
+ # target, but there's no DEPS file
340
+ self.error_ifarg(
341
+ f'Trying to resolve command-line target={t_full}:',
342
+ f'but path {t_path} has no DEPS markup file (DEPS.yml)',
343
+ arg='error-unknown-args',
344
+ error_code=EDA_DEPS_FILE_NOT_FOUND
345
+ )
349
346
  else:
350
- # If we have caller_info, then this was a recursive call from another
351
- # DEPS file. It should already have the useful error messaging:
352
-
353
- if '.' in target_node:
354
- # Likely a filename (target_node does not include path)
355
- self.error_ifarg(
356
- f'Trying to resolve target={t_full} (file?):',
357
- f'called from {caller_info},',
358
- f'File={t_node} not found in directory={t_path}',
359
- arg='error-unknown-args',
360
- error_code=EDA_DEPS_FILE_NOT_FOUND
361
- )
362
- elif not self.rel_deps_file:
363
- # target, but there's no DEPS file
364
- self.error_ifarg(
365
- f'Trying to resolve target={t_full}:',
366
- f'called from {caller_info},',
367
- f'but {t_path} has no DEPS markup file (DEPS.yml)',
368
- arg='error-unknown-args',
369
- error_code=EDA_DEPS_FILE_NOT_FOUND
370
- )
371
- else:
372
- self.warning_show_available_targets()
373
- self.error_ifarg(
374
- f'Trying to resolve target={t_full}:',
375
- f'called from {caller_info},',
376
- f'Target not found in deps_file={self.rel_deps_file}',
377
- arg='error-unknown-args',
378
- error_code=EDA_DEPS_TARGET_NOT_FOUND
379
- )
347
+ self.warning_show_available_targets()
348
+ self.error_ifarg(
349
+ f'Trying to resolve command-line target={t_full}:',
350
+ f'was not found in deps_file={self.rel_deps_file}',
351
+ arg='error-unknown-args',
352
+ error_code=EDA_DEPS_TARGET_NOT_FOUND
353
+ )
354
+
380
355
  else:
381
- debug(f'Found {target_node=} in deps_file={self.rel_deps_file}')
382
- found_target = True
356
+ # If we have caller_info, then this was a recursive call from another
357
+ # DEPS file. It should already have the useful error messaging:
358
+
359
+ if '.' in target_node:
360
+ # Likely a filename (target_node does not include path)
361
+ self.error_ifarg(
362
+ f'Trying to resolve target={t_full} (file?):',
363
+ f'called from {caller_info},',
364
+ f'File={t_node} not found in directory={t_path}',
365
+ arg='error-unknown-args',
366
+ error_code=EDA_DEPS_FILE_NOT_FOUND
367
+ )
368
+ elif not self.rel_deps_file:
369
+ # target, but there's no DEPS file
370
+ self.error_ifarg(
371
+ f'Trying to resolve target={t_full}:',
372
+ f'called from {caller_info},',
373
+ f'but {t_path} has no DEPS markup file (DEPS.yml)',
374
+ arg='error-unknown-args',
375
+ error_code=EDA_DEPS_FILE_NOT_FOUND
376
+ )
377
+ else:
378
+ self.warning_show_available_targets()
379
+ self.error_ifarg(
380
+ f'Trying to resolve target={t_full}:',
381
+ f'called from {caller_info},',
382
+ f'Target not found in deps_file={self.rel_deps_file}',
383
+ arg='error-unknown-args',
384
+ error_code=EDA_DEPS_TARGET_NOT_FOUND
385
+ )
383
386
 
384
- return found_target
387
+ return False
385
388
 
386
389
 
387
390
  def get_entry(self, target_node) -> dict:
opencos/eda.py CHANGED
@@ -204,9 +204,12 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
204
204
 
205
205
  has_all_exe = True
206
206
  has_all_in_exe_path = True
207
+ exe_path = None
207
208
  for exe in exe_list:
208
209
  assert exe != '', f'{name=} {value=} value missing "exe" {exe=}'
209
210
  p = shutil.which(exe)
211
+ if not exe_path:
212
+ exe_path = p # set on first required exe
210
213
  if not p:
211
214
  has_all_exe = False
212
215
  util.debug(f"... No, missing exe {exe}")
@@ -217,10 +220,11 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
217
220
 
218
221
  has_vsim_helper = True
219
222
  if value.get('requires_vsim_helper', False):
220
- # This tool name must be in opencos.utils.vsim_helper.TOOL_IS[name].
223
+ # This tool name must be in opencos.utils.vsim_helper.TOOL_PATH[name].
221
224
  # Special case for vsim being used by a lot of tools.
222
225
  vsim_helper.init() # only runs checks once internally
223
- has_vsim_helper = vsim_helper.TOOL_IS.get(name, False)
226
+ exe_path = vsim_helper.TOOL_PATH[name]
227
+ has_vsim_helper = bool(exe_path)
224
228
 
225
229
  has_vscode_helper = True
226
230
  needs_vscode_extensions = value.get('requires_vscode_extension', None)
@@ -254,18 +258,24 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
254
258
 
255
259
  if all((has_all_py, has_all_env, has_all_exe, has_all_in_exe_path,
256
260
  has_vsim_helper, has_vscode_helper)):
257
- exe = exe_list[0]
258
- p = shutil.which(exe)
259
- config['auto_tools_found'][name] = exe # populate key-value pairs w/ first exe in list
261
+ if exe_path:
262
+ p = exe_path
263
+ else:
264
+ p = shutil.which(exe_list[0])
265
+ config['auto_tools_found'][name] = p # populate key-value pairs w/ first exe in list
260
266
  if not quiet:
261
267
  util.info(f"Detected {name} ({p})")
262
- tool_setup(tool=name, quiet=True, auto_setup=True, warnings=warnings, config=config)
268
+ tool_setup(
269
+ tool=name, quiet=True, auto_setup=True, warnings=warnings, config=config
270
+ )
263
271
 
264
272
  return config
265
273
 
266
274
 
267
- def tool_setup(tool: str, config: dict, quiet: bool = False, auto_setup: bool = False,
268
- warnings: bool = True):
275
+ def tool_setup(
276
+ tool: str, config: dict, quiet: bool = False, auto_setup: bool = False,
277
+ warnings: bool = True
278
+ ):
269
279
  ''' Adds items to config["tools_loaded"] (set) and updates config['command_handler'].
270
280
 
271
281
  config is potentially updated for entry ['command_handler'][command] with a Tool class.
@@ -410,7 +420,8 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
410
420
 
411
421
  deferred_tokens = unparsed
412
422
  if not command:
413
- util.error("Didn't get a command!")
423
+ util.error("'eda' didn't get a command, or command is invalid (run with --help to see",
424
+ "valid commands)!")
414
425
  return 2
415
426
 
416
427
  sco = config['command_handler'][command](config=config) # sub command object
@@ -430,7 +441,12 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
430
441
  command not in config.get('command_determines_tool', []) and \
431
442
  command not in config.get('command_tool_is_optional', []):
432
443
  use_tool = which_tool(command, config)
433
- util.info(f"--tool not specified, using default for {command=}: {use_tool}")
444
+ if use_tool:
445
+ util.info(f"--tool not specified, using default for {command=}: {use_tool}")
446
+ else:
447
+ # Not all commands have a hard requirement on tool (such as 'multi') because we
448
+ # haven't examined sub-commands yet.
449
+ util.info(f'--tool not specified, will attempt to determine tool(s) for {command=}.')
434
450
  setattr(sco, 'auto_tool_applied', True)
435
451
 
436
452
  rc = check_command_handler_cls(command_obj=sco, command=command, parsed_args=parsed)
@@ -483,12 +499,60 @@ def check_command_handler_cls(command_obj:object, command:str, parsed_args) -> i
483
499
  if not isinstance(sco, cls):
484
500
  # If someone set --tool verilator for command=synth, then our 'sco' will have defaulted
485
501
  # to CommandSynth with no tool attached. If we don't have a tool set, error and return.
486
- util.warning(f"{command=} is using handling class '{type(sco)}' (but missing",
487
- f"requirement {cls}, likely because we aren't using a derived class",
488
- "for a specific tool)")
489
- parsed_tool = getattr(parsed_args, 'tool', '??')
490
- return util.error(f"EDA {command=} for tool '{parsed_tool}' is not",
491
- f"supported (this tool '{parsed_tool}' cannot run {command=})")
502
+ parsed_tool = getattr(parsed_args, 'tool', '')
503
+ auto_tool_entry = command_obj.config.get(
504
+ 'auto_tools_order', [{}])[0].get(parsed_tool, {})
505
+ if parsed_tool and not auto_tool_entry:
506
+ util.warning(
507
+ f"{command=} for tool '{parsed_tool}' is using handling class '{type(sco)}',",
508
+ f"but missing requirement {cls}, likely because the tool was not loaded",
509
+ "(not in PATH) or mis-configured (such as missing a Tool based class)"
510
+ )
511
+ return util.error(
512
+ f"EDA {command=} for tool '{parsed_tool}' cannot be run because tool",
513
+ f"'{parsed_tool}' is not known to `eda`. It does not exist in the config:",
514
+ "see informational message for --config-yml, and check that file's",
515
+ "auto_tools_order."
516
+ )
517
+ if parsed_tool:
518
+ util.warning(
519
+ f"{command=} for tool '{parsed_tool}' is using handling class '{type(sco)}',",
520
+ f"but missing requirement {cls}, likely because the tool was not loaded",
521
+ "(not in PATH) or mis-configured (such as missing a Tool based class)"
522
+ )
523
+ for k,v in auto_tool_entry.items():
524
+ if k == 'exe' or k.startswith('requires_cmd'):
525
+ util.warning(
526
+ f"tool '{parsed_tool}' has requirements that may not have been met --",
527
+ f"{k}: {v}"
528
+ )
529
+ if k == 'requires_vsim_helper':
530
+ if found_tool := vsim_helper.found():
531
+ util.warning(
532
+ f"tool '{parsed_tool}' was not found, vsim appears to be for tool",
533
+ f"'{found_tool}'"
534
+ )
535
+
536
+ return util.error(
537
+ f"EDA {command=} for tool '{parsed_tool}' is not supported (tool",
538
+ f"'{parsed_tool}' cannot run {command=}). It is likely that tool",
539
+ f"'{parsed_tool}' is not in PATH, or was unable to be loaded due to missing",
540
+ "requirements, or missing information when checking the exe version."
541
+ )
542
+
543
+ # No parsed_tool.
544
+ util.warning(
545
+ f"{command=} for default tool (--tool not set) is using handling class",
546
+ f"'{type(sco)}', but missing requirement {cls}, likely because the tool was not",
547
+ "loaded (not in PATH) or mis-configured (such as missing a Tool based class)"
548
+ )
549
+ return util.error(
550
+ f"EDA {command=} for default tool (--tool not set) is not supported (default",
551
+ f"tool cannot run {command=}). It appears that no suitable default tool to run",
552
+ f"{command=} was automatically found, was not in PATH, or was unable to be loaded",
553
+ "due to missing requirements, or missing information when checking the exe version."
554
+ )
555
+
492
556
  return 0
493
557
 
494
558
 
@@ -559,9 +623,36 @@ def main(*args):
559
623
  util.global_log.stop()
560
624
  return main_ret
561
625
 
626
+ def main_show_autocomplete_instructions() -> None:
627
+ ''' Executable script entry point - eda_show_autocomplete
628
+
629
+ from pyproject.toml::project.scripts. Shows instructions on how to enable bash autocomplete
630
+ '''
631
+ source_filepath = opencos.__file__.replace(
632
+ '__init__.py', 'eda_deps_bash_completion.bash'
633
+ )
634
+ if os.path.exists(source_filepath):
635
+ print(
636
+ f"{Colors.yellow}To enable bash autocompletion with"
637
+ f" {Colors.bold}eda{Colors.normal}{Colors.yellow} script (uv not equired):"
638
+ )
639
+ print(f"{Colors.normal}")
640
+ print(f" source {source_filepath}")
641
+ print("")
642
+ print(f"{Colors.yellow}Feel free to inspect this script prior to sourcing.")
643
+ print(f"{Colors.normal}")
644
+ sys.exit(0)
645
+ else:
646
+ util.error(f"It appears the following file(s) doe not exist: {source_filepath}")
647
+ sys.exit(1)
648
+
562
649
 
563
650
  def main_cli() -> None:
564
- ''' Returns None, will exit with return code. Entry point for package script or __main__.'''
651
+ ''' Executable script entry point - eda
652
+
653
+ from pyproject.tom::project.scripts, or from __main__.
654
+ Returns None, will exit with return code.
655
+ '''
565
656
  signal.signal(signal.SIGINT, signal_handler)
566
657
  util.global_exit_allowed = True
567
658
  # Strip eda or eda.py from sys.argv, we know who we are if called from __main__:
opencos/eda_base.py CHANGED
@@ -45,6 +45,9 @@ def print_base_help() -> None:
45
45
  def print_eda_usage_line(no_targets: bool = False, command_name='COMMAND') -> None:
46
46
  '''Prints line for eda [options] COMMAND [options] FILES|TARGETS,...'''
47
47
  print(f'{safe_emoji("🔦 ")}Usage:')
48
+ print(f' {Colors.bold}{Colors.yellow}eda_targets PATTERN')
49
+ print(f' {Colors.bold}{Colors.yellow}eda_show_autocomplete{Colors.normal}')
50
+ print()
48
51
  if no_targets:
49
52
  print(
50
53
  (f' {Colors.bold}{Colors.yellow}eda {Colors.cyan}[options]'
@@ -208,11 +211,12 @@ class Tool:
208
211
  def set_exe(self, config: dict) -> None:
209
212
  '''Sets self._EXE based on config'''
210
213
  if self._TOOL and self._TOOL in config.get('auto_tools_order', [{}])[0]:
211
- exe = config.get('auto_tools_order', [{}])[0][self._TOOL].get('exe', '')
212
- if exe and isinstance(exe, list):
213
- exe = exe[0] # pick first
214
+ # config['auto_tools_found'] has the first exe full path:
215
+ exe = config.get('auto_tools_found', {}).get(self._TOOL)
214
216
  if exe and exe != self._EXE:
215
- util.info(f'Override for {self._TOOL} using exe {exe}')
217
+ # Note that shutil.which() on the exe leaf may not match, this does NOT
218
+ # modify os.environ['PATH'].
219
+ util.debug(f'{self._TOOL} using exe: {exe}')
216
220
  self._EXE = exe
217
221
 
218
222
  def get_tool_name(self) -> str:
opencos/eda_config.py CHANGED
@@ -390,11 +390,18 @@ def tool_try_add_to_path( # pylint: disable=too-many-branches
390
390
  util.error(f'--tool setting for {tool}: {user_exe} is not an executable')
391
391
  return name
392
392
 
393
- user_exe = shutil.which(exe)
393
+ user_exe = shutil.which(user_exe)
394
394
 
395
395
  if update_config:
396
396
  if isinstance(config_exe, list):
397
397
  config['auto_tools_order'][0][name]['exe'][0] = user_exe
398
+ for index,value in enumerate(config_exe[1:]):
399
+ # update all entries, if we can, if the value is also in 'path'
400
+ # from our set --tool=Name=path/exe
401
+ new_value = os.path.join(path, os.path.split(value)[1])
402
+ if os.path.exists(new_value) and shutil.which(new_value) and \
403
+ os.access(new_value, os.X_OK):
404
+ config['auto_tools_order'][0][name]['exe'][index] = new_value
398
405
  else:
399
406
  config['auto_tools_order'][0][name]['exe'] = user_exe
400
407
  util.debug(f'For {tool=}, auto_tools_order config updated')
@@ -510,7 +510,7 @@ auto_tools_order:
510
510
  lec: opencos.tools.slang_yosys.CommandLecYosys
511
511
 
512
512
  questa:
513
- exe: qrun
513
+ exe: vsim
514
514
  requires_vsim_helper: True
515
515
  handlers:
516
516
  lint: opencos.tools.questa.CommandLintQuesta
@@ -528,13 +528,14 @@ auto_tools_order:
528
528
  elab: opencos.tools.riviera.CommandElabRiviera
529
529
  sim: opencos.tools.riviera.CommandSimRiviera
530
530
 
531
- modelsim_ase:
531
+ questa_fe:
532
532
  exe: vsim
533
533
  requires_vsim_helper: True
534
534
  handlers:
535
- lint: opencos.tools.modelsim_ase.CommandLintModelsimAse
536
- elab: opencos.tools.modelsim_ase.CommandElabModelsimAse
537
- sim: opencos.tools.modelsim_ase.CommandSimModelsimAse
535
+ lint: opencos.tools.questa_fe.CommandLintQuestaFe
536
+ elab: opencos.tools.questa_fe.CommandElabQuestaFe
537
+ sim: opencos.tools.questa_fe.CommandSimQuestaFe
538
+ flist: opencos.tools.questa_fe.CommandFListQuestaFe
538
539
 
539
540
  questa_fse: # free student edition, works similar to modelsim_ase
540
541
  exe: vsim
@@ -545,6 +546,14 @@ auto_tools_order:
545
546
  sim: opencos.tools.questa_fse.CommandSimQuestaFse
546
547
  flist: opencos.tools.questa_fse.CommandFListQuestaFse
547
548
 
549
+ modelsim_ase:
550
+ exe: vsim
551
+ requires_vsim_helper: True
552
+ handlers:
553
+ lint: opencos.tools.modelsim_ase.CommandLintModelsimAse
554
+ elab: opencos.tools.modelsim_ase.CommandElabModelsimAse
555
+ sim: opencos.tools.modelsim_ase.CommandSimModelsimAse
556
+
548
557
  iverilog:
549
558
  exe: iverilog
550
559
  handlers: