opencos-eda 0.3.15__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 (38) 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 +6 -2
  6. opencos/deps/deps_file.py +30 -9
  7. opencos/deps/deps_processor.py +99 -65
  8. opencos/deps_schema.py +8 -0
  9. opencos/docs/DEPS.md +6 -0
  10. opencos/eda.py +30 -5
  11. opencos/eda_base.py +21 -9
  12. opencos/eda_config.py +2 -1
  13. opencos/eda_config_defaults.yml +9 -1
  14. opencos/eda_tool_helper.py +84 -9
  15. opencos/files.py +41 -0
  16. opencos/tools/cocotb.py +1 -1
  17. opencos/tools/invio.py +1 -1
  18. opencos/tools/invio_yosys.py +1 -1
  19. opencos/tools/iverilog.py +1 -1
  20. opencos/tools/quartus.py +1 -1
  21. opencos/tools/questa_common.py +6 -3
  22. opencos/tools/riviera.py +5 -3
  23. opencos/tools/slang.py +1 -1
  24. opencos/tools/slang_yosys.py +36 -8
  25. opencos/tools/surelog.py +1 -1
  26. opencos/tools/verilator.py +209 -20
  27. opencos/tools/vivado.py +1 -1
  28. opencos/tools/yosys.py +155 -30
  29. opencos/util.py +5 -1
  30. opencos/utils/docker_checks.py +224 -0
  31. opencos/utils/subprocess_helpers.py +3 -1
  32. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/METADATA +1 -1
  33. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/RECORD +38 -37
  34. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/WHEEL +0 -0
  35. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/entry_points.txt +0 -0
  36. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/licenses/LICENSE +0 -0
  37. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/licenses/LICENSE.spdx +0 -0
  38. {opencos_eda-0.3.15.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",
@@ -29,6 +29,8 @@ KNOWN_EDA_COMMANDS = set([
29
29
  ])
30
30
 
31
31
  SUPPORTED_TARGET_TABLE_KEYS = set([
32
+ 'description', # optional text, for DEPS.json enjoyers
33
+ 'info', # same as 'description'
32
34
  'args',
33
35
  'defines',
34
36
  'parameters',
@@ -36,6 +38,7 @@ SUPPORTED_TARGET_TABLE_KEYS = set([
36
38
  'plusargs',
37
39
  'top',
38
40
  'deps',
41
+ 'files', # identical to 'deps', will append to deps
39
42
  'reqs',
40
43
  'multi',
41
44
  'tags',
@@ -52,6 +55,7 @@ SUPPORTED_TAG_KEYS = set([
52
55
  'with-args',
53
56
  'args',
54
57
  'deps',
58
+ 'files', # identical to 'deps', will append to deps
55
59
  'reqs',
56
60
  'defines',
57
61
  'parameters',
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':
@@ -172,6 +185,10 @@ def deps_target_get_deps_list(
172
185
  deps = entry.get(default_key, [])
173
186
  deps = dep_str2list(deps)
174
187
 
188
+ # Support for 'files' also, append to return value under 'deps'
189
+ if default_key == 'deps' and 'files' in entry:
190
+ deps += dep_str2list(entry.get('files', []))
191
+
175
192
  # Strip commented out list entries, strip blank strings, preserve non-strings
176
193
  ret = []
177
194
  for dep in deps:
@@ -204,11 +221,15 @@ def deps_list_target_sanitize(
204
221
  ret = {default_key: entry}
205
222
 
206
223
  if ret is not None:
207
- if entry_fix_deps_key and 'deps' in ret:
224
+ if entry_fix_deps_key and (('deps' in ret) or
225
+ ('files' in ret)):
208
226
  ret['deps'] = deps_target_get_deps_list(
209
227
  entry=ret, default_key='deps', deps_file=deps_file,
210
- entry_must_have_default_key=True
228
+ entry_must_have_default_key=False
211
229
  )
230
+ if 'files' in ret:
231
+ # was already appeneded to 'deps' in the ret dict
232
+ del ret['files']
212
233
  else:
213
234
  assert False, f"Can't convert to list {entry=} {default_key=} {target_node=} {deps_file=}"
214
235