opencos-eda 0.3.16__py3-none-any.whl → 0.3.17__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 (35) hide show
  1. opencos/commands/flist.py +143 -88
  2. opencos/commands/shell.py +1 -1
  3. opencos/commands/sim.py +21 -8
  4. opencos/commands/waves.py +3 -1
  5. opencos/deps/defaults.py +2 -2
  6. opencos/deps/deps_file.py +20 -7
  7. opencos/deps/deps_processor.py +3 -3
  8. opencos/eda.py +29 -5
  9. opencos/eda_base.py +21 -9
  10. opencos/eda_config.py +2 -1
  11. opencos/eda_config_defaults.yml +9 -1
  12. opencos/eda_tool_helper.py +84 -9
  13. opencos/files.py +41 -0
  14. opencos/tools/cocotb.py +1 -1
  15. opencos/tools/invio.py +1 -1
  16. opencos/tools/invio_yosys.py +1 -1
  17. opencos/tools/iverilog.py +1 -1
  18. opencos/tools/quartus.py +1 -1
  19. opencos/tools/questa_common.py +6 -3
  20. opencos/tools/riviera.py +1 -1
  21. opencos/tools/slang.py +1 -1
  22. opencos/tools/slang_yosys.py +36 -8
  23. opencos/tools/surelog.py +1 -1
  24. opencos/tools/verilator.py +209 -20
  25. opencos/tools/vivado.py +1 -1
  26. opencos/tools/yosys.py +155 -30
  27. opencos/utils/docker_checks.py +224 -0
  28. opencos/utils/subprocess_helpers.py +3 -1
  29. {opencos_eda-0.3.16.dist-info → opencos_eda-0.3.17.dist-info}/METADATA +1 -1
  30. {opencos_eda-0.3.16.dist-info → opencos_eda-0.3.17.dist-info}/RECORD +35 -34
  31. {opencos_eda-0.3.16.dist-info → opencos_eda-0.3.17.dist-info}/WHEEL +0 -0
  32. {opencos_eda-0.3.16.dist-info → opencos_eda-0.3.17.dist-info}/entry_points.txt +0 -0
  33. {opencos_eda-0.3.16.dist-info → opencos_eda-0.3.17.dist-info}/licenses/LICENSE +0 -0
  34. {opencos_eda-0.3.16.dist-info → opencos_eda-0.3.17.dist-info}/licenses/LICENSE.spdx +0 -0
  35. {opencos_eda-0.3.16.dist-info → opencos_eda-0.3.17.dist-info}/top_level.txt +0 -0
opencos/commands/flist.py CHANGED
@@ -19,7 +19,7 @@ class CommandFList(CommandDesign):
19
19
 
20
20
  command_name = 'flist'
21
21
 
22
- def __init__(self, config: dict):
22
+ def __init__(self, config: dict, **kwargs):
23
23
  CommandDesign.__init__(self, config=config)
24
24
 
25
25
  # If there's no tool attached, then we'll assume this flist is being created
@@ -38,31 +38,6 @@ class CommandFList(CommandDesign):
38
38
  'emit-cpp' : True,
39
39
  'emit-non-sources' : True, # as comments, from DEPS 'reqs'
40
40
  'emit-eda-args' : not self.flist_has_tool, # no Tool means flist for eda.
41
- 'prefix-define' : "+define+",
42
- 'prefix-parameter' : "-G",
43
- 'prefix-incdir' : "+incdir+",
44
- 'prefix-plusargs' : "+",
45
- 'prefix-v' : "",
46
- 'prefix-sv' : "",
47
- 'prefix-vhd' : "",
48
- 'prefix-cpp' : "",
49
- 'prefix-non-sources' : "", # as comments anyway.
50
- # NOTE - the defaults are for creating an flist that is suitable for 'eda', which for
51
- # defines means: optionally single-quote the entire thing, double-quote string values
52
- # only.
53
- # Tool classes should override if they want to set safe-mode-defines=True.
54
- # Tool classes may also avoid these args entirely in their derived CommandFList class
55
- 'safe-mode-defines' : not self.flist_has_tool, # no Tool
56
- 'bracket-quote-define': False,
57
- 'single-quote-define': False,
58
- 'quote-define' : self.flist_has_tool, # Tool, this is NOT for eda.
59
- 'equal-define' : True,
60
- 'escape-define-value': False,
61
- 'quote-define-value' : False,
62
- 'bracket-quote-path' : False,
63
- 'single-quote-path' : False,
64
- 'double-quote-path' : False,
65
- 'quote-path' : True,
66
41
  'build-script' : "", # we don't want this to error either
67
42
 
68
43
  'print-to-stdout': False,
@@ -71,6 +46,49 @@ class CommandFList(CommandDesign):
71
46
  'emit-rel-path' : False,
72
47
  }
73
48
 
49
+ # Derived classes many choose to not include ALL the args, so their --help is less
50
+ # cluttered, for features they don't or can't support.
51
+ # Note - take care when referencing them! Use self.args.get(key, '') or
52
+ # self.args.get(key, False). Do NOT use self.args[key]:
53
+ if kwargs.get('include_prefix_args', True):
54
+ self.flist_args.update({
55
+ 'prefix-define' : "+define+",
56
+ 'prefix-parameter' : "-G",
57
+ 'prefix-incdir' : "+incdir+",
58
+ 'prefix-plusargs' : "+",
59
+ 'prefix-v' : "",
60
+ 'prefix-sv' : "",
61
+ 'prefix-vhd' : "",
62
+ 'prefix-cpp' : "",
63
+ 'prefix-non-sources' : "", # as comments anyway.
64
+ })
65
+ if kwargs.get('include_quote_args', True) or \
66
+ kwargs.get('include_format_define_args', True):
67
+ self.flist_args.update({
68
+ 'quote-define' : self.flist_has_tool, # Tool, this is NOT for eda.
69
+ 'quote-define-value' : False,
70
+ 'bracket-quote-define': False,
71
+ 'single-quote-define': False,
72
+ })
73
+ if kwargs.get('include_quote_args', True):
74
+ self.flist_args.update({
75
+ # NOTE - the defaults are for creating an flist that is suitable for 'eda', which
76
+ # for defines means: optionally single-quote the entire thing, double-quote string
77
+ # values only.
78
+ 'bracket-quote-path' : False,
79
+ 'single-quote-path' : False,
80
+ 'double-quote-path' : False,
81
+ 'quote-path' : True,
82
+ })
83
+ if kwargs.get('include_format_define_args', True):
84
+ self.flist_args.update({
85
+ # Tool classes should override if they want to set safe-mode-defines=True.
86
+ # Tool classes may also avoid these args entirely in their derived constructor
87
+ 'safe-mode-defines' : not self.flist_has_tool, # no Tool
88
+ 'equal-define' : True,
89
+ 'escape-define-value': False,
90
+ })
91
+
74
92
 
75
93
  self.args.update({
76
94
  'eda-dir' : 'eda.flist', # user can specify eda-dir if files are generated.
@@ -81,6 +99,9 @@ class CommandFList(CommandDesign):
81
99
  'print-to-stdout': "do not save file, print to stdout",
82
100
  })
83
101
 
102
+ self.info_tool_string = ''
103
+
104
+
84
105
  def process_tokens(
85
106
  self, tokens: list , process_all: bool = True, pwd: str = os.getcwd()
86
107
  ) -> list:
@@ -117,7 +138,7 @@ class CommandFList(CommandDesign):
117
138
  '''Returns formatted list of str for known defines'''
118
139
 
119
140
  ret = []
120
- prefix = strip_all_quotes(self.args['prefix-define'])
141
+ prefix = strip_all_quotes(self.args.get('prefix-define', '+define+'))
121
142
  for d, value in self.defines.items():
122
143
 
123
144
  if value is None:
@@ -126,36 +147,39 @@ class CommandFList(CommandDesign):
126
147
 
127
148
  # else, value exists:
128
149
  safe_mode_guard_str_value = bool(
129
- self.args['safe-mode-defines'] and isinstance(value, str) and ' ' in value
150
+ self.args.get('safe-mode-defines', False)
151
+ and isinstance(value, str)
152
+ and ' ' in value
130
153
  )
131
154
 
132
- if self.args['bracket-quote-define']:
155
+ if self.args.get('bracket-quote-define', False):
133
156
  qd1 = "{"
134
157
  qd2 = "}"
135
- elif self.args['single-quote-define']:
158
+ elif self.args.get('single-quote-define', False):
136
159
  qd1 = "'"
137
160
  qd2 = "'"
138
- elif self.args['quote-define']:
161
+ elif self.args.get('quote-define', False):
139
162
  qd1 = '"'
140
163
  qd2 = '"'
141
164
  else:
142
165
  qd1 = ''
143
166
  qd2 = ''
144
167
 
145
- if self.args['equal-define']:
168
+ if self.args.get('equal-define', False):
146
169
  ed1 = '='
147
170
  else:
148
171
  ed1 = ' '
149
172
 
150
- if self.args['escape-define-value']:
173
+ if self.args.get('escape-define-value', False):
151
174
  value = value.replace('\\', '\\\\').replace('"', '\\"')
152
- if self.args['quote-define-value']:
175
+ if self.args.get('quote-define-value', False):
153
176
  value = '"' + value + '"'
154
177
  if safe_mode_guard_str_value:
155
178
  value = strip_outer_quotes(value.strip('\n'))
156
179
  value = '"' + value + '"'
157
180
 
158
- if self.args['quote-define'] and value.startswith('"') and value.endswith('"'):
181
+ if self.args.get('quote-define', False) and isinstance(value, str) and \
182
+ value.startswith('"') and value.endswith('"'):
159
183
  # If you wanted your define to look like:
160
184
  # +define+"NAME=VALUE", but VALUE also has double quotes wrapping it,
161
185
  # it's unlikely to work so we'll optimistically so escape the " wrapping value.
@@ -181,16 +205,16 @@ class CommandFList(CommandDesign):
181
205
  '''
182
206
  ret = []
183
207
  for x in self.args.get('unprocessed-plusargs', []) + self.args.get('sim-plusargs', []):
184
- if self.args['prefix-plusargs']:
208
+ if self.args.get('prefix-plusargs', '+'):
185
209
  if x.startswith('+'):
186
210
  x = x[1:] # strip leading +
187
- x = self.args['prefix-plusargs'] + x
211
+ x = self.args.get('prefix-plusargs', '+') + x
188
212
  ret.append(x)
189
213
  return ret
190
214
 
191
215
  def get_flist_parameter_list(self) -> list:
192
216
  '''Returns formatted list of str for parameters'''
193
- prefix = strip_all_quotes(self.args['prefix-parameter'])
217
+ prefix = strip_all_quotes(self.args.get('prefix-parameter', '-G'))
194
218
  return parameters_dict_get_command_list(
195
219
  params=self.parameters, arg_prefix=prefix, for_flist=True
196
220
  )
@@ -256,16 +280,14 @@ class CommandFList(CommandDesign):
256
280
  return
257
281
 
258
282
  if self.config['tool']:
259
- tool_string = f' (with --tool={self.config["tool"]})'
260
- else:
261
- tool_string = ''
283
+ self.info_tool_string = f' (with --tool={self.config["tool"]})'
262
284
 
263
285
  # if config['tool'] is set, but self.flist_has_tool is False, we're likely using
264
286
  # this default handler CommandFList and the Tool class hasn't defined what they
265
287
  # do. In this case, simply warn that this will emit a non-tool specific default flist
266
288
  # intended for use by `eda`:
267
289
  if self.config['tool'] and not self.flist_has_tool:
268
- util.warning(f'For command="flist"{tool_string}, there is no tool',
290
+ util.warning(f'For command="flist"{self.info_tool_string}, there is no tool',
269
291
  'specific handler for producing an flist. The default eda flist will',
270
292
  'be emitted')
271
293
  # If this happens, you'll likely want the Tool based defines (that were never set
@@ -294,120 +316,153 @@ class CommandFList(CommandDesign):
294
316
  self.create_work_dir()
295
317
  self.run_dep_commands()
296
318
 
319
+ self.write_flist()
320
+
321
+ self.write_eda_config_and_args()
322
+ self.run_post_tool_dep_commands()
323
+
324
+
325
+ def write_flist(self) -> None:
326
+ '''Derived classes may override, uses self.args to decide if:
327
+
328
+ -- writing to stdout or file
329
+ -- all self.args knobs for how a define/incdir/file/parameter is presented
330
+
331
+ It may be more convenient to override CommandFList.get_write_flist_lines()
332
+ instead.
333
+ '''
334
+ if self.args['print-to-stdout']:
335
+ for line in self.get_write_flist_lines(add_comment_lines=False):
336
+ print(line)
337
+ print()
338
+ else:
339
+ util.debug(f"Opening {self.args['out']} for writing")
340
+ with open(self.args['out'], 'w', encoding='utf-8') as fo:
341
+ for line in self.get_write_flist_lines(add_comment_lines=True):
342
+ print(line, file=fo)
343
+ util.info(f"Created file: {self.args['out']}")
344
+
345
+
346
+ def get_write_flist_lines(
347
+ self, add_comment_lines: bool = False
348
+ ) -> list:
349
+ '''Derived classes may override, uses self.args to decide if:
350
+
351
+ -- all self.args knobs for how a define/incdir/file/parameter is presented
352
+
353
+ Derived classes can override to ignore a lot of the self.args configuration,
354
+ and instead follow the basic flow of dumping an flist, but how they see fit
355
+ for a given Tool class
356
+ '''
357
+
358
+ ret = []
359
+
297
360
  pq1 = ""
298
361
  pq2 = "" # pq = path quote
299
- if not self.args['quote-path']:
362
+ if not self.args.get('quote-path', False):
300
363
  pass # if we decide to make one of the below default, this will override
301
- elif self.args['bracket-quote-path']:
364
+ elif self.args.get('bracket-quote-path', False):
302
365
  pq1 = "{"
303
366
  pq2 = "}"
304
- elif self.args['single-quote-path']:
367
+ elif self.args.get('single-quote-path', False):
305
368
  pq1 = "'"
306
369
  pq2 = "'"
307
- elif self.args['double-quote-path']:
370
+ elif self.args.get('double-quote-path', False):
308
371
  pq1 = '"'
309
372
  pq2 = '"'
310
373
 
311
- if self.args['print-to-stdout']:
312
- fo = None
313
- print()
314
- else:
315
- util.debug(f"Opening {self.args['out']} for writing")
316
- fo = open( # pylint: disable=consider-using-with
317
- self.args['out'], 'w', encoding='utf-8'
318
- )
319
- print(f"## {self.args=}", file=fo)
374
+ if add_comment_lines:
375
+ ret.append(f"## {self.args=}")
320
376
 
321
377
  if self.args['emit-non-sources']:
322
378
  if self.files_non_source:
323
- print('## reqs (non-source files that are dependencies):', file=fo)
324
- prefix = strip_all_quotes(self.args['prefix-non-sources'])
379
+ ret.append('## reqs (non-source files that are dependencies):')
380
+ prefix = strip_all_quotes(self.args.get('prefix-non-sources', ''))
325
381
  for f in self.files_non_source:
326
382
  if self.args['emit-rel-path']:
327
383
  f = os.path.relpath(f)
328
- print('## ' + prefix + pq1 + f + pq2, file=fo)
384
+ ret.append('## ' + prefix + pq1 + f + pq2)
329
385
 
330
386
  if self.args['emit-eda-args']:
331
387
  for newline in self.get_flist_eda_args_list():
332
- print(newline, file=fo)
388
+ ret.append(newline)
333
389
 
334
390
  defines_lines = self.get_flist_defines_list()
335
391
  if not self.args['emit-define'] and defines_lines:
336
- util.warning(f'Command "flist"{tool_string}, has defines present but they were not',
337
- f'included in the output flist: {defines_lines}')
392
+ util.warning(
393
+ f'Command "flist"{self.info_tool_string}, has defines present but they were not',
394
+ f'included in the output flist: {defines_lines}'
395
+ )
338
396
 
339
397
  parameter_lines = self.get_flist_parameter_list()
340
398
  if not self.args['emit-parameter'] and parameter_lines:
341
- util.warning(f'Command "flist"{tool_string}, has parameters present but they were not',
342
- f'included in the output flist: {parameter_lines}')
399
+ util.warning(
400
+ f'Command "flist"{self.info_tool_string}, has parameters present but they were not',
401
+ f'included in the output flist: {parameter_lines}'
402
+ )
343
403
 
344
404
  plusarg_lines = self.get_flist_plusargs_list()
345
405
  if not self.args['emit-plusargs'] and plusarg_lines:
346
- util.warning(f'Command "flist"{tool_string}, has plusargs present but they were not',
347
- f'included in the output flist: {plusarg_lines}')
406
+ util.warning(
407
+ f'Command "flist"{self.info_tool_string}, has plusargs present but they were not',
408
+ f'included in the output flist: {plusarg_lines}'
409
+ )
348
410
 
349
411
  if self.args['emit-define']:
350
412
  for newline in defines_lines:
351
- print(newline, file=fo)
413
+ ret.append(newline)
352
414
 
353
415
  if self.args['emit-parameter']:
354
416
  for newline in parameter_lines:
355
- print(newline, file=fo)
417
+ ret.append(newline)
356
418
 
357
419
 
358
420
  if self.args['emit-incdir']:
359
- prefix = strip_all_quotes(self.args['prefix-incdir'])
421
+ prefix = strip_all_quotes(self.args.get('prefix-incdir', '+incdir+'))
360
422
  for i in self.incdirs:
361
423
  if self.args['emit-rel-path']:
362
424
  i = os.path.relpath(i)
363
- print(prefix + pq1 + i + pq2, file=fo)
425
+ ret.append(prefix + pq1 + i + pq2)
364
426
 
365
427
  if self.args['emit-plusargs']:
366
428
  for newline in plusarg_lines:
367
- print(newline, file=fo)
429
+ ret.append(newline)
368
430
 
369
431
 
370
432
  # Hook for derived classes to optionally print additional custom args, prior to
371
433
  # any files:
372
434
  for newline in self.get_additional_flist_args_list():
373
- print(newline, file=fo)
435
+ ret.append(newline)
374
436
 
375
437
  if self.args['emit-v']:
376
- prefix = strip_all_quotes(self.args['prefix-v'])
438
+ prefix = strip_all_quotes(self.args.get('prefix-v', ''))
377
439
  for f in self.files_v:
378
440
  if self.args['emit-rel-path']:
379
441
  f = os.path.relpath(f)
380
- print(prefix + pq1 + f + pq2, file=fo)
442
+ ret.append(prefix + pq1 + f + pq2)
381
443
 
382
444
  if self.args['emit-sv']:
383
- prefix = strip_all_quotes(self.args['prefix-sv'])
445
+ prefix = strip_all_quotes(self.args.get('prefix-sv', ''))
384
446
  for f in self.files_sv:
385
447
  if self.args['emit-rel-path']:
386
448
  f = os.path.relpath(f)
387
- print(prefix + pq1 + f + pq2, file=fo)
449
+ ret.append(prefix + pq1 + f + pq2)
388
450
  if self.args['emit-vhd']:
389
- prefix = strip_all_quotes(self.args['prefix-vhd'])
451
+ prefix = strip_all_quotes(self.args.get('prefix-vhd', ''))
390
452
  for f in self.files_vhd:
391
453
  if self.args['emit-rel-path']:
392
454
  f = os.path.relpath(f)
393
- print(prefix + pq1 + f + pq2, file=fo)
455
+ ret.append(prefix + pq1 + f + pq2)
394
456
  if self.args['emit-cpp']:
395
- prefix = strip_all_quotes(self.args['prefix-cpp'])
457
+ prefix = strip_all_quotes(self.args.get('prefix-cpp', ''))
396
458
  for f in self.files_cpp:
397
459
  if self.args['emit-rel-path']:
398
460
  f = os.path.relpath(f)
399
- print(prefix + pq1 + f + pq2, file=fo)
461
+ ret.append(prefix + pq1 + f + pq2)
400
462
 
401
463
  # Hook for derived classes to optionally print additional flist items after
402
464
  # any files:
403
465
  for newline in self.get_additional_flist_files_list():
404
- print(newline, file=fo)
405
-
406
- if self.args['print-to-stdout']:
407
- print() # don't need to close fo (None)
408
- else:
409
- fo.close()
410
- util.info(f"Created file: {self.args['out']}")
466
+ ret.append(newline)
411
467
 
412
- self.write_eda_config_and_args()
413
- self.run_post_tool_dep_commands()
468
+ return ret
opencos/commands/shell.py CHANGED
@@ -20,7 +20,7 @@ from opencos.utils import status_constants
20
20
 
21
21
 
22
22
  class CommandShell(CommandDesign):
23
- '''Base class command handler for: eda sim ...'''
23
+ '''Base class command handler for: eda shell ...'''
24
24
 
25
25
  command_name = 'shell'
26
26
 
opencos/commands/sim.py CHANGED
@@ -12,11 +12,11 @@ Note that CommandSim is also a base class for opencos.commands.elab.CommandElab.
12
12
  # pylint: disable=too-many-arguments
13
13
 
14
14
  import os
15
+ from pathlib import Path
15
16
  import shlex
16
17
 
17
- from pathlib import Path
18
18
 
19
- from opencos import util, export_helper
19
+ from opencos import export_helper, util
20
20
  from opencos.eda_base import CommandDesign, Tool
21
21
  from opencos.utils import status_constants
22
22
 
@@ -122,6 +122,8 @@ class CommandSim(CommandDesign): # pylint: disable=too-many-public-methods
122
122
  'license-queue': False,
123
123
  'library-map': [],
124
124
  'add-top-library': [],
125
+ 'docker-run': False,
126
+ 'docker-image': ''
125
127
  })
126
128
  self.args_help.update({
127
129
  'pre-sim-tcl': (
@@ -187,6 +189,16 @@ class CommandSim(CommandDesign): # pylint: disable=too-many-public-methods
187
189
  ' you may need to --add-top-library=unisim.glbl for it to work with Xilinx'
188
190
  ' based projects that require "glbl" to be present at simulation $root scope.'
189
191
  ),
192
+ 'docker-run': (
193
+ 'Run the simulation using "docker run" with image specified by'
194
+ ' --docker-image=NAME. Assumes the entrypoint can be /bin/bash and your --tool'
195
+ ' executable is installed in PATH. NOTE: docker-in-docker is not supported yet,'
196
+ ' if you are already in docker this option is not allowed and will fail checks.'
197
+ ),
198
+ 'docker-image': (
199
+ 'Used with arg --docker. IMAGE used by "docker run" for the sim, lint, or'
200
+ ' elab eda command.'
201
+ )
190
202
 
191
203
  })
192
204
 
@@ -544,7 +556,8 @@ class CommandSim(CommandDesign): # pylint: disable=too-many-public-methods
544
556
  compile_line_breaks: bool = True,
545
557
  elaborate_line_breaks: bool = False,
546
558
  simulate_line_breaks: bool = False,
547
- simulate_sh_fname: str = 'simulate.sh'
559
+ simulate_sh_fname: str = 'simulate.sh',
560
+ all_sh_fname: str = 'all.sh'
548
561
  ) -> None:
549
562
  '''Writes compile.sh, elaborate.sh, simulate.sh (if present), all.sh to work-dir
550
563
 
@@ -554,9 +567,9 @@ class CommandSim(CommandDesign): # pylint: disable=too-many-public-methods
554
567
 
555
568
  all_lists = [] # list - of - (command-list)
556
569
  if self.has_pre_compile_dep_shell_commands:
557
- all_lists = [
570
+ all_lists.append(
558
571
  ['./pre_compile_dep_shell_commands.sh']
559
- ]
572
+ )
560
573
 
561
574
  if compile_lists:
562
575
  util.write_shell_command_file(dirpath=self.args['work-dir'], filename='compile.sh',
@@ -577,11 +590,11 @@ class CommandSim(CommandDesign): # pylint: disable=too-many-public-methods
577
590
  all_lists.append(['./' + simulate_sh_fname])
578
591
 
579
592
  if self.has_post_tool_dep_shell_commands:
580
- all_lists = [
593
+ all_lists.append(
581
594
  ['./post_tool_dep_shell_commands.sh']
582
- ]
595
+ )
583
596
 
584
- util.write_shell_command_file(dirpath=self.args['work-dir'], filename='all.sh',
597
+ util.write_shell_command_file(dirpath=self.args['work-dir'], filename=all_sh_fname,
585
598
  command_lists=all_lists)
586
599
 
587
600
  self.write_eda_config_and_args()
opencos/commands/waves.py CHANGED
@@ -53,7 +53,9 @@ class CommandWaves(CommandDesign):
53
53
  + ' to stdout',
54
54
  })
55
55
 
56
- def get_versions_of_tool(self, tool: str) -> str:
56
+ def get_versions_of_tool( # pylint: disable=unused-argument
57
+ self, tool: str, **kwargs
58
+ ) -> str:
57
59
  '''Similar to Tool.get_versions(), returns the version of 'tool' for tools like:
58
60
 
59
61
  - vaporview
opencos/deps/defaults.py CHANGED
@@ -1,11 +1,11 @@
1
1
  ''' opencos.deps.defaults -- pymodule for defaults referenced by other modules here'''
2
2
 
3
3
 
4
- DEPS_FILE_EXTS = set([
4
+ DEPS_FILE_EXTS = [
5
5
  '.yml', '.yaml', '.toml', '.json',
6
6
  # Treat no extension DEPS as YAML.
7
7
  ''
8
- ])
8
+ ]
9
9
 
10
10
  ROOT_TABLE_KEYS_NOT_TARGETS = set([
11
11
  "DEFAULTS",
opencos/deps/deps_file.py CHANGED
@@ -12,7 +12,7 @@ import toml
12
12
 
13
13
  from opencos import util
14
14
  from opencos.deps.defaults import DEPS_FILE_EXTS, ROOT_TABLE_KEYS_NOT_TARGETS
15
- from opencos.util import debug, error
15
+ from opencos.util import debug, error, warning
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, \
@@ -28,14 +28,27 @@ def deps_data_get_all_targets(data: dict) -> list:
28
28
  return [x for x in data.keys() if (x not in ROOT_TABLE_KEYS_NOT_TARGETS and
29
29
  is_valid_target_name(x))]
30
30
 
31
-
31
+ _GET_DEPS_MARKUP_FILE_WARNINGS = set()
32
32
  def get_deps_markup_file(base_path: str) -> str:
33
33
  '''Returns one of DEPS.yml, DEPS.yaml, DEPS.toml, DEPS.json'''
34
+ found = False
35
+ deps_fpath = ''
34
36
  for suffix in DEPS_FILE_EXTS:
35
- deps_file = os.path.join(base_path, 'DEPS' + suffix)
36
- if os.path.isfile(deps_file):
37
- return deps_file
38
- return ''
37
+ fpath = os.path.join(base_path, 'DEPS' + suffix)
38
+ if os.path.isfile(fpath):
39
+ if not found:
40
+ found = True
41
+ deps_fpath = fpath
42
+ else:
43
+ if fpath not in _GET_DEPS_MARKUP_FILE_WARNINGS:
44
+ warning(
45
+ f'Ignoring DEPS file at "{fpath}", already found and using: {deps_fpath}'
46
+ )
47
+
48
+ # add to global set if found or not (isfile(fpath) is True):
49
+ _GET_DEPS_MARKUP_FILE_WARNINGS.add(fpath)
50
+
51
+ return deps_fpath
39
52
 
40
53
 
41
54
  def deps_markup_safe_load(
@@ -53,7 +66,7 @@ def deps_markup_safe_load(
53
66
  '''
54
67
  data = {}
55
68
  _, file_ext = os.path.splitext(filepath)
56
- if file_ext in ['', '.yml', 'yaml']:
69
+ if file_ext in ['', '.yml', '.yaml']:
57
70
  # treat DEPS as YAML.
58
71
  data = yaml_safe_load(filepath=filepath, only_root_line_numbers=only_root_line_numbers)
59
72
  elif file_ext == '.toml':
@@ -532,10 +532,10 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
532
532
  with_args_matched_list = []
533
533
  for k,v in with_args.items():
534
534
  with_args_matched_list.append(False)
535
- if not apply_tag_items_tools:
535
+ if not all((apply_tag_items_tools, apply_tag_items_commands)):
536
536
  # If we didn't previously match with-tools (if with-tools was present),
537
- # then we may not match the args, b/c those are tool dependend in the
538
- # Command handling class.
537
+ # or match with-commands, then we may not match the args, b/c those are
538
+ # command + tool dependent in the Command handling class.
539
539
  pass
540
540
  elif k not in self.command_design_ref.args:
541
541
  warning(f'{tagname=} in {self.caller_info}:',
opencos/eda.py CHANGED
@@ -20,10 +20,11 @@ from pathlib import Path
20
20
  import opencos
21
21
  from opencos import util, eda_config, eda_base
22
22
  from opencos.eda_base import Command, Tool, which_tool, print_eda_usage_line
23
- from opencos.eda_tool_helper import pretty_info_handler_tools
23
+ from opencos.eda_tool_helper import DOCKER_TOOL_NOTE, pretty_info_handler_tools, \
24
+ tool_loaded_but_flagged_as_docker_only, get_all_handler_commands
24
25
  from opencos.files import safe_shutil_which
25
26
  from opencos.util import safe_emoji, Colors
26
- from opencos.utils import vsim_helper, vscode_helper
27
+ from opencos.utils import vsim_helper, vscode_helper, docker_checks
27
28
  from opencos.utils import status_constants, str_helpers, subprocess_helpers
28
29
 
29
30
  # Configure util:
@@ -99,9 +100,17 @@ def init_config(
99
100
  def get_all_commands_help_str(config: dict) -> str:
100
101
  '''Returns a str of help based on what commands eda supports, from config'''
101
102
  all_commands_help = []
103
+ all_handler_commands = get_all_handler_commands(config=config)
102
104
  max_command_str_len = max(len(s) for s in config.get('DEFAULT_HANDLERS_HELP', {}).keys())
103
105
  for key, value in config.get('DEFAULT_HANDLERS_HELP', {}).items():
104
- all_commands_help.append(f' {key:<{max_command_str_len}} - {value.strip()}')
106
+ all_commands_help.append(
107
+ f' {Colors.bcyan}{key:<{max_command_str_len}}{Colors.normal} - {value.strip()}'
108
+ )
109
+ if key in all_handler_commands:
110
+ all_commands_help[-1] += (
111
+ f'\n {" ":<{max_command_str_len}} '
112
+ f'--tool: {Colors.cyan}{", ".join(all_handler_commands[key])}{Colors.normal}'
113
+ )
105
114
  if all_commands_help:
106
115
  all_commands_help = [
107
116
  f'Where {Colors.byellow}COMMAND{Colors.normal} is one of:',
@@ -277,13 +286,21 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
277
286
  if not exe_path:
278
287
  exe_path = p # set on first required exe
279
288
  if not p:
280
- has_all_exe = False
289
+ # skip the fail (has_all_exe=False) if we can run via docker.
290
+ if tool_cfg.get('docker_support', None):
291
+ if not docker_checks.docker_ok():
292
+ has_all_exe = False
293
+ util.debug("... No, missing docker requirements")
294
+ else:
295
+ has_all_exe = False
281
296
  util.debug(f"... No, missing exe {exe}")
282
297
  for req in tool_cfg.get('requires_in_exe_path', []):
283
298
  if p and req and str(Path(req)) not in str(Path(p)):
284
299
  has_all_in_exe_path = False
285
300
  util.debug(f"... No, missing path requirement {req}")
286
301
 
302
+
303
+
287
304
  has_vsim_helper = True
288
305
  if tool_cfg.get('requires_vsim_helper', False):
289
306
  # This tool name must be in opencos.utils.vsim_helper.TOOL_PATH[name].
@@ -327,6 +344,9 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
327
344
  p = exe_path
328
345
  else:
329
346
  p = safe_shutil_which(exe_list[0])
347
+ if not p and tool_cfg.get('docker_support', None) and docker_checks.docker_ok():
348
+ # Sets note that this tool is loaded, but only avail via docker:
349
+ p = DOCKER_TOOL_NOTE
330
350
  config['auto_tools_found'][name] = p # populate key-value pairs w/ first exe in list
331
351
  if name not in config['tools_loaded']:
332
352
  config['tools_loaded'].append(name)
@@ -357,7 +377,7 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
357
377
  )
358
378
  return config
359
379
 
360
- # If --tool was not set, look at config['auto_tools_found'] should be populated based on
380
+ # If --tool was not set, look at config['auto_tools_order'] should be populated based on
361
381
  # config['tools'], and if we called this method (auto_tool_setup(...)) with tool= and command=
362
382
  # arg(s). Call set_command_handlers for each command/tool we need to:
363
383
  for _command, list_of_tools in config['auto_tools_order'].items():
@@ -365,6 +385,10 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
365
385
  for _tool in list_of_tools:
366
386
  if _tool not in config['tools_loaded']:
367
387
  continue
388
+ if tool_loaded_but_flagged_as_docker_only(tool=_tool, config=config):
389
+ # do not let this tool be the auto-handler for a command (aka, it is the
390
+ # chosen tool if --tool=TOOL arg not present) if it can only run via docker.
391
+ continue
368
392
 
369
393
  set_command_handler(
370
394
  command=_command, tool=_tool, config=config, auto_setup=(not tool),