opencos-eda 0.3.9__py3-none-any.whl → 0.3.11__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 (74) hide show
  1. opencos/commands/deps_help.py +89 -120
  2. opencos/commands/export.py +7 -2
  3. opencos/commands/multi.py +3 -3
  4. opencos/commands/sim.py +14 -16
  5. opencos/commands/synth.py +1 -2
  6. opencos/commands/upload.py +192 -4
  7. opencos/commands/waves.py +8 -8
  8. opencos/deps/deps_commands.py +6 -6
  9. opencos/deps/deps_file.py +82 -79
  10. opencos/deps/deps_processor.py +129 -50
  11. opencos/docs/Architecture.md +45 -0
  12. opencos/docs/ConnectingApps.md +29 -0
  13. opencos/docs/DEPS.md +199 -0
  14. opencos/docs/Debug.md +77 -0
  15. opencos/docs/DirectoryStructure.md +22 -0
  16. opencos/docs/Installation.md +117 -0
  17. opencos/docs/OcVivadoTcl.md +63 -0
  18. opencos/docs/OpenQuestions.md +7 -0
  19. opencos/docs/README.md +13 -0
  20. opencos/docs/RtlCodingStyle.md +54 -0
  21. opencos/docs/eda.md +147 -0
  22. opencos/docs/oc_cli.md +135 -0
  23. opencos/eda.py +175 -41
  24. opencos/eda_base.py +180 -50
  25. opencos/eda_config.py +62 -16
  26. opencos/eda_config_defaults.yml +21 -4
  27. opencos/eda_deps_bash_completion.bash +37 -15
  28. opencos/files.py +26 -1
  29. opencos/tools/cocotb.py +5 -5
  30. opencos/tools/invio.py +2 -2
  31. opencos/tools/invio_yosys.py +2 -1
  32. opencos/tools/iverilog.py +3 -3
  33. opencos/tools/quartus.py +113 -115
  34. opencos/tools/questa_common.py +3 -4
  35. opencos/tools/riviera.py +3 -3
  36. opencos/tools/slang.py +11 -7
  37. opencos/tools/slang_yosys.py +1 -0
  38. opencos/tools/surelog.py +4 -3
  39. opencos/tools/verilator.py +5 -4
  40. opencos/tools/vivado.py +307 -176
  41. opencos/tools/yosys.py +4 -4
  42. opencos/util.py +6 -3
  43. opencos/utils/dict_helpers.py +31 -0
  44. opencos/utils/markup_helpers.py +2 -2
  45. opencos/utils/str_helpers.py +7 -0
  46. opencos/utils/subprocess_helpers.py +3 -3
  47. opencos/utils/vscode_helper.py +2 -2
  48. opencos/utils/vsim_helper.py +58 -22
  49. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/METADATA +1 -1
  50. opencos_eda-0.3.11.dist-info/RECORD +94 -0
  51. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/entry_points.txt +1 -0
  52. opencos/tests/__init__.py +0 -0
  53. opencos/tests/custom_config.yml +0 -13
  54. opencos/tests/deps_files/command_order/DEPS.yml +0 -44
  55. opencos/tests/deps_files/error_msgs/DEPS.yml +0 -55
  56. opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -4
  57. opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
  58. opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -50
  59. opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -54
  60. opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -4
  61. opencos/tests/helpers.py +0 -354
  62. opencos/tests/test_build.py +0 -12
  63. opencos/tests/test_deps_helpers.py +0 -207
  64. opencos/tests/test_deps_schema.py +0 -30
  65. opencos/tests/test_eda.py +0 -921
  66. opencos/tests/test_eda_elab.py +0 -110
  67. opencos/tests/test_eda_synth.py +0 -150
  68. opencos/tests/test_oc_cli.py +0 -25
  69. opencos/tests/test_tools.py +0 -404
  70. opencos_eda-0.3.9.dist-info/RECORD +0 -99
  71. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/WHEEL +0 -0
  72. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE +0 -0
  73. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE.spdx +0 -0
  74. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/top_level.txt +0 -0
opencos/commands/waves.py CHANGED
@@ -15,10 +15,10 @@ handler).
15
15
  # pylint: disable=R0801
16
16
 
17
17
  import os
18
- import shutil
19
18
 
20
19
  from opencos import util
21
20
  from opencos.eda_base import CommandDesign
21
+ from opencos.files import safe_shutil_which
22
22
 
23
23
 
24
24
  class CommandWaves(CommandDesign):
@@ -120,10 +120,10 @@ class CommandWaves(CommandDesign):
120
120
 
121
121
  # TODO(drew): this feels a little customized per-tool, perhaps there's a better
122
122
  # way to abstract this configuration for adding other waveform viewers.
123
- # For example for each command we also have to check shutil.which, because normal Tool
123
+ # For example for each command we also have to check safe_shutil_which, because normal Tool
124
124
  # classs should work even w/out PATH, but these don't use Tool classes.
125
125
  if wave_file.endswith('.wdb'):
126
- if 'vivado' in self.config['tools_loaded'] and shutil.which('vivado'):
126
+ if 'vivado' in self.config['tools_loaded'] and safe_shutil_which('vivado'):
127
127
  tcl_name = wave_file + '.waves.tcl'
128
128
  with open( tcl_name, 'w', encoding='utf-8') as fo :
129
129
  print( 'current_fileset', file=fo)
@@ -141,20 +141,20 @@ class CommandWaves(CommandDesign):
141
141
  f"{self.VSIM_TOOLS} in PATH")
142
142
  elif wave_file.endswith('.fst'):
143
143
  if ('vaporview' in self.config['tools_loaded'] or \
144
- 'surfer' in self.config['tools_loaded']) and shutil.which('code'):
144
+ 'surfer' in self.config['tools_loaded']) and safe_shutil_which('code'):
145
145
  command_list = ['code', '-n', '.', wave_file]
146
146
  self.exec(os.path.dirname(wave_file), command_list)
147
- elif 'gtkwave' in self.config['tools_loaded'] and shutil.which('gtkwave'):
147
+ elif 'gtkwave' in self.config['tools_loaded'] and safe_shutil_which('gtkwave'):
148
148
  command_list = ['gtkwave', wave_file]
149
149
  self.exec(os.path.dirname(wave_file), command_list)
150
150
  else:
151
151
  self.error(f"Don't know how to open {wave_file} without GtkWave in PATH")
152
152
  elif wave_file.endswith('.vcd'):
153
153
  if ('vaporview' in self.config['tools_loaded'] or \
154
- 'surfer' in self.config['tools_loaded']) and shutil.which('code'):
154
+ 'surfer' in self.config['tools_loaded']) and safe_shutil_which('code'):
155
155
  command_list = ['code', '-n', '.', wave_file]
156
156
  self.exec(os.path.dirname(wave_file), command_list)
157
- elif 'gtkwave' in self.config['tools_loaded'] and shutil.which('gtkwave'):
157
+ elif 'gtkwave' in self.config['tools_loaded'] and safe_shutil_which('gtkwave'):
158
158
  command_list = ['gtkwave', wave_file]
159
159
  self.exec(os.path.dirname(wave_file), command_list)
160
160
  elif self._vsim_available(from_tools=self.VSIM_VCD_TOOLS):
@@ -172,7 +172,7 @@ class CommandWaves(CommandDesign):
172
172
  self, from_tools: list = VSIM_TOOLS
173
173
  ) -> bool:
174
174
  '''Returns True if 'vsim' is available (Questa or Modelsim)'''
175
- return bool(shutil.which('vsim')) and \
175
+ return bool(safe_shutil_which('vsim')) and \
176
176
  any(x in self.config['tools_loaded'] for x in from_tools)
177
177
 
178
178
 
@@ -13,10 +13,11 @@ my_target:
13
13
  import os
14
14
  from pathlib import Path
15
15
  import re
16
- import shutil
17
16
 
18
- from opencos.util import debug, error, warning, ShellCommandList
19
17
  from opencos.deps.defaults import SUPPORTED_COMMAND_KEYS, COMMAND_ATTRIBUTES
18
+ from opencos.files import safe_shutil_which, PY_EXE
19
+ from opencos.util import debug, error, warning, ShellCommandList
20
+
20
21
 
21
22
  THISPATH = os.path.dirname(__file__)
22
23
  PEAKRDL_CLEANUP_PY = os.path.join(THISPATH, '..', 'peakrdl_cleanup.py')
@@ -221,7 +222,7 @@ def parse_deps_peakrdl( # pylint: disable=too-many-locals
221
222
  + ' however it is not enabled in edy.py - eda.config[dep_command_enables]')
222
223
  return None
223
224
 
224
- if not shutil.which('peakrdl'):
225
+ if not safe_shutil_which('peakrdl'):
225
226
  error('peakrdl: is not present in shell path, or the python package is not avaiable,' \
226
227
  + f' yet we encountered a peakrdl command in {target_path=} {target_node=}')
227
228
  return None
@@ -255,11 +256,10 @@ def parse_deps_peakrdl( # pylint: disable=too-many-locals
255
256
 
256
257
  sv_files += [ f'peakrdl/{top}_pkg.sv', f'peakrdl/{top}.sv' ]
257
258
 
258
-
259
259
  shell_commands = [
260
- [ 'peakrdl', 'regblock', '-o', str(Path('peakrdl/'))] + args_list,
260
+ [ safe_shutil_which('peakrdl'), 'regblock', '-o', str(Path('peakrdl/'))] + args_list,
261
261
  # Edit file to apply some verilator waivers, etc, from peakrdl_cleanup.py:
262
- [ 'python3', PEAKRDL_CLEANUP_PY, str(Path(f'peakrdl/{top}.sv')),
262
+ [ PY_EXE, PEAKRDL_CLEANUP_PY, str(Path(f'peakrdl/{top}.sv')),
263
263
  str(Path(f'peakrdl/{top}.sv')) ],
264
264
  ]
265
265
 
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:
@@ -5,15 +5,17 @@ CommandDesign ref object
5
5
  '''
6
6
 
7
7
  import argparse
8
+ import copy
8
9
  import os
9
10
 
10
11
  from opencos import files
11
12
  from opencos import eda_config
12
- from opencos.util import debug, info, warning, error, read_tokens_from_dot_f, \
13
+ from opencos.util import Colors, debug, info, warning, error, read_tokens_from_dot_f, \
13
14
  patch_args_for_dir, load_env_file
14
15
  from opencos.utils.str_helpers import dep_str2list
15
16
  from opencos.deps.deps_file import deps_target_get_deps_list
16
17
  from opencos.deps.deps_commands import deps_commands_handler
18
+ from opencos.utils.dict_helpers import dict_diff
17
19
 
18
20
  from opencos.deps.defaults import SUPPORTED_TARGET_TABLE_KEYS, SUPPORTED_TAG_KEYS, \
19
21
  SUPPORTED_DEP_KEYS_BY_TYPE
@@ -53,6 +55,14 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
53
55
  self.deps_dir, _ = os.path.split(deps_file)
54
56
  self.caller_info = caller_info
55
57
 
58
+ # Check if it's a Command instead of CommandDesign:
59
+ self.is_command_design = bool(
60
+ getattr(command_design_ref, 'process_plusarg', None) and \
61
+ getattr(command_design_ref, 'set_parameter', None) and \
62
+ isinstance(getattr(command_design_ref, 'incdirs', None), list)
63
+ )
64
+
65
+
56
66
  assert isinstance(deps_entry, dict), \
57
67
  f'{deps_entry=} for {target_node=} in {deps_file=} must be a dict'
58
68
  assert command_design_ref is not None, \
@@ -79,6 +89,8 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
79
89
 
80
90
  def apply_defines(self, defines_dict: dict) -> None:
81
91
  '''Given defines_dict, applies them to our self.command_design_ref obj'''
92
+ if not self.is_command_design:
93
+ return
82
94
  if not isinstance(defines_dict, dict):
83
95
  self.error(f"{defines_dict=} is not type dict, can't apply defines,",
84
96
  f"in {self.caller_info}")
@@ -97,6 +109,8 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
97
109
 
98
110
  def apply_plusargs(self, plusargs_dict: dict) -> None:
99
111
  '''Given plusarsg_dict, applies them to our self.command_design_ref obj'''
112
+ if not self.is_command_design:
113
+ return
100
114
  if not isinstance(plusargs_dict, dict):
101
115
  self.error(f"{plusargs_dict=} is not type dict, can't apply plusargs,",
102
116
  f"in {self.caller_info}")
@@ -109,6 +123,8 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
109
123
 
110
124
  def apply_parameters(self, parameters_dict: dict) -> None:
111
125
  '''Given parameters_dict, applies them to our self.command_design_ref obj'''
126
+ if not self.is_command_design:
127
+ return
112
128
  if not isinstance(parameters_dict, dict):
113
129
  self.error(f"{parameters_dict=} is not type dict, can't apply defines,",
114
130
  f"in {self.caller_info}")
@@ -124,6 +140,8 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
124
140
 
125
141
  def apply_incdirs(self, incdirs_list:list) -> None:
126
142
  '''Given incdirs_list, applies them to our self.command_design_ref obj'''
143
+ if not self.is_command_design:
144
+ return
127
145
  if not isinstance(incdirs_list, (str, list)):
128
146
  self.error(f"{incdirs_list=} is not type str/list, can't apply incdirs",
129
147
  f"in {self.caller_info}")
@@ -135,38 +153,59 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
135
153
  debug(f'Added include dir {abspath} from {self.caller_info}')
136
154
 
137
155
 
138
- def _apply_args_check_tools(self, tokens: list) -> list:
139
- '''Helper for apply_args(list), returns list strips --tool args under certain conditions'''
156
+ def _apply_args_check_tools(self, tokens: list, tagname: str) -> list:
157
+ '''Helper for apply_args(list), returns list strips --tool args under certain conditions
158
+
159
+ Basically, we want to see if a DEPS target want to set arg --tool, if so:
160
+ Accept it:
161
+ - this is not a tool class
162
+ - tool was automatically chosen by eda.py's auto-tool-order (--tool not set at
163
+ CLI)
164
+ - accepting means set self.args['tool'], so we can respawn with this.
165
+ Reject/Warn if:
166
+ - previous tool was not automatically applied (--tool was set at CLI)
167
+ and you're trying to change the --tool value.
168
+ - This can happen if you have complicated DEPS targets trying to set the
169
+ tool to different values.
170
+ '''
140
171
 
172
+ parser = argparse.ArgumentParser(
173
+ prog='deps_processor --tool', add_help=False, allow_abbrev=False
174
+ )
175
+ parser.add_argument('--tool', default='')
176
+ try:
177
+ parsed, unparsed = parser.parse_known_args(tokens + [''])
178
+ tokens2 = list(filter(None, unparsed))
179
+ except argparse.ArgumentError:
180
+ error('deps_processor --tool problem attempting to parse_known_args for:',
181
+ f'{tokens}, {self.caller_info} {tagname}')
182
+ tokens2 = tokens
183
+
184
+ _tool_class = 'tool' in self.args
141
185
  _orig_tool = self.args.get('tool', '')
142
- if not self.command_design_ref.auto_tool_applied and \
143
- any(x.startswith('--tool') for x in tokens) and _orig_tool:
144
- warn_tool = ''
145
- for i, item in enumerate(list(tokens)):
146
- if item == '--tool':
147
- if tokens[i + 1] != _orig_tool:
148
- warn_tool = tokens[i + 1]
149
- tokens[i : i+2] = ['', ''] # remove this and next arg
150
- elif item.startswith('--tool='):
151
- if item[7:] != _orig_tool:
152
- warn_tool = item
153
- tokens[i] = '' # remove just this arg.
154
-
155
- if warn_tool:
156
- warning(
157
- f'Attempting to set --tool {warn_tool} from DEPS',
158
- f'(file={self.deps_file}:{self.target_node})',
159
- f'however the tool was already chosen as: {_orig_tool}. The --tool arg will',
160
- f'not be applied from: {tokens}'
161
- )
162
186
 
163
- tokens = [item for item in tokens if item != ''] # remove blanks
187
+ if not self.command_design_ref.auto_tool_applied and \
188
+ _tool_class and parsed.tool and _orig_tool \
189
+ and parsed.tool != _orig_tool:
190
+ # tool arg present, --tool in this DEPS args, and tool already set.
191
+ warning(
192
+ f'Attempting to set --tool {parsed.tool} from DEPS',
193
+ f'(file={self.deps_file}:{self.target_node})',
194
+ f'however the tool was already chosen as: {_orig_tool}. The --tool arg will',
195
+ f'not be applied from: {tokens}'
196
+ )
197
+ elif (self.command_design_ref.auto_tool_applied or not _tool_class) and parsed.tool:
198
+ # tool arg wasn't present (not a Tool class), or it was auto-applied,
199
+ # then add the arg anyway so we can later respawn with the correct tool.
200
+ self.args['tool'] = parsed.tool
201
+ debug(f'setting arg.tool to {parsed.tool=} from {self.caller_info}')
164
202
 
165
- return tokens
203
+ # remove blanks, '--tool[=value| value]' removed.
204
+ return [item for item in tokens2 if item != '']
166
205
 
167
206
 
168
- def apply_args( # pylint: disable=too-many-locals,too-many-branches
169
- self, args_list:list
207
+ def apply_args( # pylint: disable=too-many-locals,too-many-branches,too-many-statements
208
+ self, args_list:list, tagname: str = ''
170
209
  ) -> list:
171
210
  '''Given args_list, applies them to our self.command_design_ref obj
172
211
 
@@ -174,9 +213,15 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
174
213
  unparsed args will show up as eda.py warnings, but will not fail. Most callers do not
175
214
  use the unparsed args from this method.
176
215
  '''
216
+ if tagname:
217
+ tagname = f'{tagname=}'
218
+
177
219
  if not isinstance(args_list, (str, list)):
178
220
  self.error(f"{args_list=} is not type str/list, can't apply args",
179
- f"in {self.caller_info}")
221
+ f"in {self.caller_info} {tagname}")
222
+
223
+ prev_args = copy.deepcopy(self.args)
224
+
180
225
  tokens = dep_str2list(args_list)
181
226
 
182
227
  # patch args relative to the DEPS (if self.deps_dir exists) so things like
@@ -211,7 +256,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
211
256
  tokens2 = list(filter(None, unparsed))
212
257
  except argparse.ArgumentError:
213
258
  error('deps_processor -f/--input-file, problem attempting to parse_known_args for:',
214
- f'{tokens}')
259
+ f'{tokens}, {self.caller_info} {tagname}')
215
260
  tokens2 = tokens
216
261
 
217
262
  if parsed.input_file:
@@ -242,13 +287,12 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
242
287
  # the user may think they were allowed to set --tool, but in our flow the Command handler
243
288
  # (self.command_design_ref) has already been chosen, so setting the tool can have
244
289
  # strange side-effects.
290
+ _tool_class = 'tool' in self.args
245
291
  _orig_tool = self.args.get('tool', '')
246
- tokens = self._apply_args_check_tools(tokens=tokens)
247
- if not tokens:
248
- return []
292
+ tokens = self._apply_args_check_tools(tokens=tokens, tagname=tagname)
249
293
 
250
294
  debug(f'deps_processor - custom apply_args with {tokens=}',
251
- f'from {self.caller_info}')
295
+ f'from {self.caller_info} {tagname}')
252
296
  _, unparsed = self.command_design_ref.run_argparser_on_list(
253
297
  tokens=tokens
254
298
  )
@@ -256,7 +300,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
256
300
  # Annoying, but check for plusargs in unparsed, and have referenced CommandDesign
257
301
  # or CommandSim class handle it with process_plusarg.
258
302
  for arg in list(unparsed):
259
- if arg.startswith('+'):
303
+ if arg.startswith('+') and self.is_command_design:
260
304
  self.command_design_ref.process_plusarg(plusarg=arg, pwd=self.target_path)
261
305
  unparsed.remove(arg)
262
306
 
@@ -264,10 +308,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
264
308
  for arg in list(unparsed):
265
309
  # Since this isn't command line, we have to assume for files, the path is relative
266
310
  # to this DEPS file.
267
- if os.path.isabs(arg):
268
- target = arg
269
- else:
270
- target = os.path.join(self.deps_dir, arg)
311
+ target = self.correct_a_deps_target(target=arg, deps_dir=self.deps_dir)
271
312
 
272
313
  file_exists, fpath, forced_extension = files.get_source_file(target)
273
314
  if file_exists:
@@ -279,6 +320,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
279
320
 
280
321
  else:
281
322
  if not os.path.isdir(target) and \
323
+ self.is_command_design and \
282
324
  self.command_design_ref.resolve_target_core(
283
325
  target=target, no_recursion=False, caller_info=self.caller_info,
284
326
  error_on_not_found=False
@@ -293,7 +335,10 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
293
335
  warning(f'For {self.command_design_ref.command_name}:' \
294
336
  + f' in {self.caller_info} has unknown args {unparsed=}')
295
337
 
296
- if self.command_design_ref.auto_tool_applied and _orig_tool != self.args.get('tool', ''):
338
+ if (self.command_design_ref.auto_tool_applied or not _tool_class) and \
339
+ _orig_tool != self.args.get('tool', ''):
340
+ # If there was an auto tool applied (tool class or not) then attempt to pick
341
+ # a new sub-command-object with that tool.
297
342
  debug(f'deps_processor.apply_args: tool changed, {self.args["tool"]=}, will attempt',
298
343
  f'to respawn the job using original args: {self.config["eda_original_args"]}')
299
344
  self.command_design_ref.tool_changed_respawn = {
@@ -302,6 +347,12 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
302
347
  'from': self.caller_info,
303
348
  }
304
349
 
350
+ diff_args = dict_diff(prev_args, self.args)
351
+ if diff_args:
352
+ args_list = [item for item in args_list if item != ''] # remove blanks
353
+ info(f'{Colors.yellow}{self.caller_info} {tagname}{Colors.green}:',
354
+ f'applying args for {args_list}: {Colors.cyan}{diff_args}')
355
+
305
356
  return unparsed
306
357
 
307
358
  def apply_reqs(self, reqs_list:list) -> None:
@@ -551,9 +602,9 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
551
602
  ' skipped args due to args disabled.')
552
603
  if args_list:
553
604
  # This will apply knowns args to the target dep:
554
- info(f'{tagname=} in {self.caller_info=}:',
605
+ debug(f'{tagname=} in {self.caller_info=}:',
555
606
  f'applying args for {args_list=}')
556
- self.apply_args(args_list)
607
+ self.apply_args(args_list, tagname=tagname)
557
608
 
558
609
  elif key == 'reqs':
559
610
  reqs_list = deps_target_get_deps_list(entry=value,
@@ -578,11 +629,16 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
578
629
  # apply replace-config-tools
579
630
  # This will replace lists (compile-waivers).
580
631
  tool_config = value.get('replace-config-tools', {}).get(tool, None)
581
- if tool_config and not deps_tags_enables.get('replace-config-tools', None):
632
+ ref_has_tool_config = isinstance(
633
+ getattr(self.command_design_ref, 'tool_config', None), dict
634
+ )
635
+ if tool_config and (not deps_tags_enables.get('replace-config-tools', None) or \
636
+ not ref_has_tool_config):
582
637
  tool_config = None
583
638
  warning(f'{tagname=} in {self.caller_info}:',
584
- ' skipped replace-config-tools b/c it is disabled.')
585
- if tool_config and isinstance(tool_config, dict):
639
+ 'skipped replace-config-tools b/c it is disabled or not present for',
640
+ 'this tool and command')
641
+ if ref_has_tool_config and tool_config and isinstance(tool_config, dict):
586
642
  # apply it to self.tool_config:
587
643
  info(f'{tagname=} in {self.caller_info}:',
588
644
  f'applying replace-config-tools for {tool=}: {tool_config}')
@@ -595,11 +651,13 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
595
651
  # apply additive-config-tools
596
652
  # This will append to lists (compile-waivers)
597
653
  tool_config = value.get('additive-config-tools', {}).get(tool, None)
598
- if tool_config and not deps_tags_enables.get('additive-config-tools', None):
654
+ if tool_config and (not deps_tags_enables.get('additive-config-tools', None) or \
655
+ not ref_has_tool_config):
599
656
  tool_config = None
600
657
  warning(f'{tagname=} in {self.caller_info}:',
601
- ' skipped additive-config-tools b/c it is disabled.')
602
- if tool_config and isinstance(tool_config, dict):
658
+ ' skipped additive-config-tools b/c it is disable or not present for',
659
+ 'this tool and command')
660
+ if ref_has_tool_config and tool_config and isinstance(tool_config, dict):
603
661
  # apply it to self.tool_config:
604
662
  info(f'{tagname=} in {self.caller_info}:',
605
663
  f'applying additive-config-tools for {tool=}: {tool_config}')
@@ -774,6 +832,12 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
774
832
 
775
833
  shell_commands_list, work_dir_add_srcs_list = self.get_commands(commands=commands, dep=dep)
776
834
 
835
+ if shell_commands_list and \
836
+ not self.is_command_design:
837
+ warning(f'Not applying shell commands from {self.caller_info}, not supported',
838
+ 'for this tool and command')
839
+ return
840
+
777
841
  # add these commands lists to self.command_design_ref:
778
842
  # Process all shell_commands_list:
779
843
  # This will track each shell command with its target_node and target_path
@@ -850,7 +914,8 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
850
914
 
851
915
 
852
916
  elif isinstance(dep, str) and \
853
- any(dep.startswith(x) for x in ['+define+', '+incdir+']):
917
+ any(dep.startswith(x) for x in ['+define+', '+incdir+']) and \
918
+ self.is_command_design:
854
919
  # Note: we still support +define+ and +incdir in the deps list.
855
920
  # check for compile-time Verilog style plusarg, which are supported under targets
856
921
  # These are not run-time Verilog style plusargs comsumable from within the .sv:
@@ -860,10 +925,11 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
860
925
  else:
861
926
  # If we made it this far, dep better be a str type.
862
927
  assert isinstance(dep, str), f'{dep=} {type(dep)=} must be str'
863
- dep_path = os.path.join(self.target_path, dep)
928
+ dep_path = self.correct_a_deps_target(target=dep, deps_dir=self.target_path)
864
929
  debug(f"Got dep {dep_path=} for in {self.caller_info}")
865
930
 
866
- if dep_path in self.command_design_ref.targets_dict or \
931
+ if self.is_command_design and \
932
+ dep_path in self.command_design_ref.targets_dict or \
867
933
  dep_path in deps_targets_to_resolve:
868
934
  debug(" - already processed, skipping")
869
935
  else:
@@ -888,3 +954,16 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
888
954
  # dep3: 'command_tuple',
889
955
  # }
890
956
  return deps_targets_to_resolve
957
+
958
+
959
+ def correct_a_deps_target(self, target: str, deps_dir: str) -> str:
960
+ '''Give a target/file in a deps: list, return a patched version
961
+
962
+ - $VAR replacment
963
+ - relative to current DEPS file dir (or not if it was abspath)
964
+ '''
965
+ if self.config['deps_expandvars_enable']:
966
+ target = os.path.expandvars(target)
967
+ if not os.path.isabs(target):
968
+ target = os.path.join(deps_dir, target)
969
+ return target