opencos-eda 0.3.9__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 (36) 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 +46 -9
  5. opencos/eda_base.py +8 -4
  6. opencos/eda_config.py +8 -1
  7. opencos/eda_deps_bash_completion.bash +37 -15
  8. opencos/tools/questa_common.py +1 -2
  9. opencos/tools/verilator.py +1 -0
  10. opencos/utils/str_helpers.py +7 -0
  11. opencos/utils/vsim_helper.py +47 -22
  12. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.10.dist-info}/METADATA +1 -1
  13. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.10.dist-info}/RECORD +18 -36
  14. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.10.dist-info}/entry_points.txt +1 -0
  15. opencos/tests/__init__.py +0 -0
  16. opencos/tests/custom_config.yml +0 -13
  17. opencos/tests/deps_files/command_order/DEPS.yml +0 -44
  18. opencos/tests/deps_files/error_msgs/DEPS.yml +0 -55
  19. opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -4
  20. opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
  21. opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -50
  22. opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -54
  23. opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -4
  24. opencos/tests/helpers.py +0 -354
  25. opencos/tests/test_build.py +0 -12
  26. opencos/tests/test_deps_helpers.py +0 -207
  27. opencos/tests/test_deps_schema.py +0 -30
  28. opencos/tests/test_eda.py +0 -921
  29. opencos/tests/test_eda_elab.py +0 -110
  30. opencos/tests/test_eda_synth.py +0 -150
  31. opencos/tests/test_oc_cli.py +0 -25
  32. opencos/tests/test_tools.py +0 -404
  33. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.10.dist-info}/WHEEL +0 -0
  34. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.10.dist-info}/licenses/LICENSE +0 -0
  35. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.10.dist-info}/licenses/LICENSE.spdx +0 -0
  36. {opencos_eda-0.3.9.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.
@@ -613,9 +623,36 @@ def main(*args):
613
623
  util.global_log.stop()
614
624
  return main_ret
615
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
+
616
649
 
617
650
  def main_cli() -> None:
618
- ''' 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
+ '''
619
656
  signal.signal(signal.SIGINT, signal_handler)
620
657
  util.global_exit_allowed = True
621
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')
@@ -1,14 +1,34 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
3
  # How to use?
4
- # 1) copy this script locally and source it.
4
+ # 1) run:
5
+ # eda_show_autocomplete
6
+ # for instructions, will likely show you the location of this file,
7
+ # eda_deps_bash_completion.bash, for you to source. Does not require uv.
8
+ #
9
+ # 2) Given the result from (1), and if you use uv, add the following to your
10
+ # ~/.bashrc:
11
+ # # Make sure 'eda' is a valid executable when not in a venv:
12
+ # if ! type -P "eda" &>/dev/null; then
13
+ # uv tool install --python 3.14 opencos-eda >/dev/null 2>&1
14
+ # echo "uv tool installed opencos-eda"
15
+ # fi
16
+ # if [ -f PATH-FROM-STEP-1 ]; then
17
+ # . PATH-FROM-STEP-1
18
+ # fi
19
+ #
20
+ # 3) copy this script locally and source it.
5
21
  # For example:
6
22
  # > source ~/sh/eda_deps_bash_completion.bash
7
- # You can put this in your .bashrc.
8
- # 2) From you venv activate script:
9
- # (bottom of activate script, assuming python3.10):
23
+ # You can put this in your .bashrc. Note you will need a venv active or
24
+ # "eda" isn't in your path yet.
25
+ #
26
+ # 4) Have it sourced when you start your venv. Note this doesn't play as nicely
27
+ # with "uv" due to having a less stable .venv, but you can add this to your
28
+ # VENV_NAME/bin/activate script:
29
+ # (bottom of activate script, assuming python3.XX):
10
30
  # script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
11
- # . $script_dir/../lib/python3.10/site-packages/opencos/eda_deps_bash_completion.bash
31
+ # . $script_dir/../lib/python3.XX/site-packages/opencos/eda_deps_bash_completion.bash
12
32
 
13
33
 
14
34
  # scripts via pyproject.toml:
@@ -17,6 +37,15 @@ SCRIPT_NAME="eda"
17
37
  # how we get the completion targets:
18
38
  EXTRACTION_SCRIPT_NAME="eda_targets"
19
39
 
40
+ EDA_WORDS="sim lint elab synth flist proj multi tools-multi sweep build \
41
+ waves upload open export shell targets lec \
42
+ +define+ +incdirs+ \
43
+ --help --quiet --verbose --debug \
44
+ --tool --seed --top --keep --force --fake --lint --work-dir \
45
+ --stop-before-compile --stop-after-compile --stop-before-elaborate \
46
+ --export --export-run --export-json \
47
+ "
48
+
20
49
  _eda_script_completion() {
21
50
 
22
51
  # Set up for additional completions
@@ -28,22 +57,15 @@ _eda_script_completion() {
28
57
  if [[ $(type -P "$EXTRACTION_SCRIPT_NAME") ]]; then
29
58
  keys=$("$EXTRACTION_SCRIPT_NAME" "$cur")
30
59
  if [[ -n "$keys" ]]; then
31
- completions=($(compgen -W "$keys" -- "$cur"))
60
+ completions=($(compgen -W "$keys $EDA_WORDS" -- "$cur"))
32
61
  fi
33
62
  fi
34
63
 
35
64
  if [ -z "${completions}" ]; then
36
65
  # If we didn't find anything in a DEPS.[yml|yaml|toml|json], then use:
37
- # 1. a bunch of known eda words or args.
38
- eda_words="multi sim elab flist build synth waves proj waves targets \
39
- +define+ +incdirs+ \
40
- --help --quiet --verbose --debug \
41
- --tool --seed --top --keep --force --fake --lint --work-dir \
42
- --stop-before-compile --stop-after-compile --stop-before-elaborate \
43
- --export --export-run --export-json \
44
- "
66
+ # -- a bunch of known eda words or args.
45
67
  # 2. a glob the current word to mimic normal bash:
46
- completions=($(compgen -W "${eda_words}" -G "${cur}*" -- "$cur"))
68
+ completions=($(compgen -W "$EDA_WORDS" -G "${cur}*" -- "$cur"))
47
69
  fi
48
70
 
49
71
  COMPREPLY=("${completions[@]}")
@@ -25,7 +25,7 @@ class ToolQuesta(Tool):
25
25
  _EXE = 'vsim'
26
26
 
27
27
  starter_edition = False
28
- use_vopt = shutil.which('vopt') # vopt exists in qrun/vsim framework, and we'll use it.
28
+ use_vopt = False # set manually or by get_versions() (after __init__ has set self._EXE)
29
29
  sim_exe = '' # vsim or qrun
30
30
  sim_exe_base_path = ''
31
31
  questa_major = None
@@ -33,7 +33,6 @@ class ToolQuesta(Tool):
33
33
 
34
34
  def __init__(self, config: dict):
35
35
  super().__init__(config=config)
36
- self.args['part'] = 'xcu200-fsgd2104-2-e'
37
36
 
38
37
  def get_versions(self) -> str:
39
38
  if self._VERSION:
@@ -30,6 +30,7 @@ class ToolVerilator(Tool):
30
30
  def get_versions(self) -> str:
31
31
  if self._VERSION:
32
32
  return self._VERSION
33
+ # __init__ would have set self.EXE to full path.
33
34
  path = shutil.which(self._EXE)
34
35
  if not path:
35
36
  self.error(f'"{self._EXE}" not in path or not installed, see {self._URL})')
@@ -203,3 +203,10 @@ def strip_ansi_color(text: str) -> str:
203
203
  '''Strip ANSI color characters from str'''
204
204
  ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
205
205
  return ansi_escape.sub('', text)
206
+
207
+ def get_shorter_path_str_rel_vs_abs(rel_path: str) -> str:
208
+ '''Returns the shorter of relative path (input arg) vs abspath (converted)'''
209
+ apath = os.path.abspath(rel_path)
210
+ if len(apath) < len(rel_path):
211
+ return apath
212
+ return rel_path