opencos-eda 0.2.48__py3-none-any.whl → 0.2.49__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 (54) hide show
  1. opencos/__init__.py +4 -2
  2. opencos/_version.py +10 -7
  3. opencos/commands/flist.py +8 -7
  4. opencos/commands/multi.py +13 -14
  5. opencos/commands/sweep.py +3 -2
  6. opencos/deps/__init__.py +0 -0
  7. opencos/deps/defaults.py +69 -0
  8. opencos/deps/deps_commands.py +419 -0
  9. opencos/deps/deps_file.py +326 -0
  10. opencos/deps/deps_processor.py +670 -0
  11. opencos/deps_schema.py +7 -8
  12. opencos/eda.py +84 -64
  13. opencos/eda_base.py +572 -316
  14. opencos/eda_config.py +80 -14
  15. opencos/eda_extract_targets.py +22 -14
  16. opencos/eda_tool_helper.py +33 -7
  17. opencos/export_helper.py +166 -86
  18. opencos/export_json_convert.py +31 -23
  19. opencos/files.py +2 -1
  20. opencos/hw/__init__.py +0 -0
  21. opencos/{oc_cli.py → hw/oc_cli.py} +9 -4
  22. opencos/names.py +0 -4
  23. opencos/peakrdl_cleanup.py +13 -7
  24. opencos/seed.py +19 -11
  25. opencos/tests/helpers.py +3 -2
  26. opencos/tests/test_deps_helpers.py +35 -32
  27. opencos/tests/test_eda.py +36 -29
  28. opencos/tests/test_eda_elab.py +5 -3
  29. opencos/tests/test_eda_synth.py +1 -1
  30. opencos/tests/test_oc_cli.py +1 -1
  31. opencos/tests/test_tools.py +3 -2
  32. opencos/tools/iverilog.py +2 -2
  33. opencos/tools/modelsim_ase.py +2 -2
  34. opencos/tools/riviera.py +1 -1
  35. opencos/tools/slang.py +1 -1
  36. opencos/tools/surelog.py +1 -1
  37. opencos/tools/verilator.py +1 -1
  38. opencos/tools/vivado.py +1 -1
  39. opencos/tools/yosys.py +4 -3
  40. opencos/util.py +374 -468
  41. opencos/utils/__init__.py +0 -0
  42. opencos/utils/markup_helpers.py +98 -0
  43. opencos/utils/str_helpers.py +111 -0
  44. opencos/utils/subprocess_helpers.py +108 -0
  45. {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.49.dist-info}/METADATA +1 -1
  46. opencos_eda-0.2.49.dist-info/RECORD +88 -0
  47. {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.49.dist-info}/entry_points.txt +1 -1
  48. opencos/deps_helpers.py +0 -1346
  49. opencos_eda-0.2.48.dist-info/RECORD +0 -79
  50. /opencos/{pcie.py → hw/pcie.py} +0 -0
  51. {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.49.dist-info}/WHEEL +0 -0
  52. {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.49.dist-info}/licenses/LICENSE +0 -0
  53. {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.49.dist-info}/licenses/LICENSE.spdx +0 -0
  54. {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.49.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,670 @@
1
+ ''' opencos.deps.deps_processor -- module is less about "parsing" and more about "processing"
2
+
3
+ a DEPS markup files targets (applying deps, reqs, commands, tags, incdirs, defines, etc) to a
4
+ CommandDesign ref object
5
+ '''
6
+
7
+ import os
8
+
9
+ from opencos import files
10
+ from opencos import eda_config
11
+ from opencos.util import debug, info, warning, error
12
+ from opencos.utils.str_helpers import dep_str2list
13
+ from opencos.deps.deps_file import deps_target_get_deps_list
14
+ from opencos.deps.deps_commands import deps_commands_handler
15
+
16
+ from opencos.deps.defaults import SUPPORTED_TARGET_TABLE_KEYS, SUPPORTED_TAG_KEYS, \
17
+ SUPPORTED_DEP_KEYS_BY_TYPE
18
+
19
+ class DepsProcessor: # pylint: disable=too-many-instance-attributes
20
+ '''DepsProcessor -- called by eda_base resolve_target_core(..)
21
+
22
+ example usage:
23
+ my_dp = DepsProcessor(
24
+ command_design_ref = self, # aka, your CommandDesign obj
25
+ deps_entry = <some-table-found-in-deps-data>,
26
+ target = <target-str-we-were-looking-for>
27
+ target_path = <filepath, for debug>
28
+ target_node = <target leaf str, for debug>
29
+ deps_file = <original DEPS.[markup-ext] for debug>
30
+ caller_info = <str info for debug>
31
+ )
32
+
33
+ '''
34
+
35
+ def __init__(self, command_design_ref, deps_entry: dict, target: str,
36
+ target_path: str, target_node: str, deps_file: str, caller_info: str):
37
+ '''
38
+ command_design_ref (eda.CommandDesign),
39
+ deps_entry (dict, target in DEPS.yml file)
40
+ target_node (str) -- key in DEPS.yml that got us the deps_entry, used for debug
41
+ deps_file (str) -- file, used for debug
42
+ caller_info (str) -- used for debug
43
+ '''
44
+
45
+ self.command_design_ref = command_design_ref
46
+ self.deps_entry = deps_entry
47
+ self.target = target
48
+ self.target_path = target_path
49
+ self.target_node = target_node # for debug
50
+ self.deps_file = deps_file # for debug
51
+ self.caller_info = caller_info
52
+
53
+ assert isinstance(deps_entry, dict), \
54
+ f'{deps_entry=} for {target_node=} in {deps_file=} must be a dict'
55
+ assert command_design_ref is not None, \
56
+ 'called DepsProcessor.__init__, but no ref to CommandDesign object (is None)'
57
+
58
+ # named eda commands in the target:
59
+ # If this deps_entry has a 'sim', 'build', etc command entry for this target, grab that
60
+ # because it can set defines or other things specific to an eda command ('sim', for example)
61
+ self.entry_eda_command = self.deps_entry.get(command_design_ref.command_name, {})
62
+
63
+ # alias some of the self.command_design_ref values
64
+ self.command_name = self.command_design_ref.command_name # str, for debug
65
+ self.args = self.command_design_ref.args # dict
66
+ self.config = self.command_design_ref.config # dict
67
+ self.set_arg = self.command_design_ref.set_arg # method
68
+ self.error = self.command_design_ref.error # method.
69
+
70
+ # If there are expanded eda commands in
71
+ # self.command_design_ref.config['command_handler'].keys(), then make note of that now.
72
+ self.known_eda_commands = getattr(
73
+ self.command_design_ref, 'config', {}
74
+ ).get('command_handler', {}).keys()
75
+
76
+
77
+ def apply_defines(self, defines_dict: dict):
78
+ '''Given defines_dict, applies them to our self.command_design_ref obj'''
79
+ if not isinstance(defines_dict, dict):
80
+ self.error(f"{defines_dict=} is not type dict, can't apply defines,",
81
+ f"in {self.caller_info}")
82
+ for k,v in defines_dict.items():
83
+ if v is None or v == '':
84
+ self.command_design_ref.process_plusarg(f'+define+{k}')
85
+ else:
86
+ # %PWD% and %SEED% substiutions:
87
+ if v and isinstance(v, str):
88
+ if v.startswith('%PWD%/') or v.startswith('"%PWD%/'):
89
+ v = v.replace('%PWD%', os.path.abspath(self.target_path))
90
+ if v.startswith('%SEED%') or v.startswith('"%SEED%'):
91
+ v = v.replace('%SEED%', str(self.args.get('seed', 1)))
92
+ self.command_design_ref.process_plusarg(f'+define+{k}={v}')
93
+
94
+ def apply_incdirs(self, incdirs_list:list):
95
+ '''Given incdirs_list, applies them to our self.command_design_ref obj'''
96
+ if not isinstance(incdirs_list, (str, list)):
97
+ self.error(f"{incdirs_list=} is not type str/list, can't apply incdirs",
98
+ f"in {self.caller_info}")
99
+ incdirs_list = dep_str2list(incdirs_list)
100
+ for x in incdirs_list:
101
+ abspath = os.path.abspath(os.path.join(self.target_path, x))
102
+ if abspath not in self.command_design_ref.incdirs:
103
+ self.command_design_ref.incdirs.append(abspath)
104
+ debug(f'Added include dir {abspath} from {self.caller_info}')
105
+
106
+ def apply_args(self, args_list:list) -> list:
107
+ '''Given args_list, applies them to our self.command_design_ref obj
108
+
109
+ This will return unparsed args that weren't in the self.command_design_ref.args keys
110
+ unparsed args will show up as eda.py warnings, but will not fail. Most callers do not
111
+ use the unparsed args from this method.
112
+ '''
113
+ if not isinstance(args_list, (str, list)):
114
+ self.error(f"{args_list=} is not type str/list, can't apply args",
115
+ f"in {self.caller_info}")
116
+ tokens = dep_str2list(args_list)
117
+ # We're going to run an ArgumentParser here, which is not the most efficient
118
+ # thing to do b/c it runs on all of self.command_design_ref.args (dict) even
119
+ # if we're applying a single token.
120
+ debug(f'deps_processor - custom apply_args with {tokens=}',
121
+ f'from {self.caller_info}')
122
+ _, unparsed = self.command_design_ref.run_argparser_on_list(
123
+ tokens=tokens
124
+ )
125
+ # Annoying, but check for plusargs in unparsed, and have referenced CommandDesign
126
+ # or CommandSim class handle it with process_plusarg.
127
+ for arg in unparsed:
128
+ if arg.startswith('+'):
129
+ self.command_design_ref.process_plusarg(plusarg=arg, pwd=self.target_path)
130
+
131
+ if unparsed:
132
+ # This is only a warning - because things like CommandFlist may not have every
133
+ # one of their self.args.keys() set for a given target, such as a 'sim' target that
134
+ # has --optimize, which is not an arg for CommandFlist. But we'd still like to get an
135
+ # flist from that target.
136
+ warning(f'For {self.command_design_ref.command_name}:' \
137
+ + f' in {self.caller_info} has unknown args {unparsed=}')
138
+ return unparsed
139
+
140
+ def apply_reqs(self, reqs_list:list) -> None:
141
+ '''Given reqs_list, applies them ot our self.command_design_ref obj'''
142
+ for req in reqs_list:
143
+ req_abspath = os.path.abspath(os.path.join(self.target_path, req))
144
+ self.command_design_ref.add_file(
145
+ req_abspath, use_abspath=False, add_to_non_sources=True,
146
+ caller_info=self.caller_info
147
+ )
148
+
149
+ def process_deps_entry(self):
150
+ '''Main entry point (after creating DepsProcessor obj) to resolve a deps target
151
+
152
+ Example usage:
153
+ deps_processor = DepsProcessor(...)
154
+ deps_targets_to_resolve = deps_processor.process_deps_entry()
155
+
156
+ This will return a list of "deps" that haven't been traversed, but are needed by
157
+ the deps_processor entry (the target we're trying to resolve).
158
+
159
+ This method will apply all target features to the CommandDesign ref object as
160
+ we traverse.
161
+
162
+ Supported target keys:
163
+ -- tags (or equivalent, to support multiple define/incdir/deps for a target)
164
+ -- supports tag-name, with-tools, with-args, args, defines, incdirs, deps
165
+ ** to be applied if a tool matches.
166
+ -- TODO(drew): other features in docs/DEPS.md not yet implemented.
167
+ -- multi: ignore-this-target: - commands (handled in eda.py CommandMulti.resolve_target)
168
+ -- Named eda commands
169
+ -- (partially done) sim or other eda commands (eda.py command specific things)
170
+ basically, check the command, and apply/merge values to 'entry'?
171
+ -- args
172
+ -- defines
173
+ -- incdirs
174
+ -- top.
175
+ -- commands (not in deps)
176
+ -- deps
177
+
178
+ TODO(drew): This does not yet support conditional inclusions based on defines,
179
+ like the old DEPS files did with pattern:
180
+ SOME_DEFINE ? dep_if_define_present : dep_if_define_not_present
181
+ I would like to deprecate that in favor of 'tags'. However, likely will need
182
+ to walk the entire DEPS.yml once to populate all args/defines, and then re-
183
+ walk them to add/prune the correct tag based dependencies, or rely on it being
184
+ entirely top-down.
185
+ '''
186
+
187
+ # DEPS.yml entries have ordered keys, and process these in-order
188
+ # with how the <target> defined it.
189
+ remaining_deps_list = [] # deps items we find that are not yet processed.
190
+ for key in self.deps_entry.keys():
191
+
192
+ # Make sure DEPS target table keys are legal:
193
+ if key not in SUPPORTED_TARGET_TABLE_KEYS and \
194
+ key not in self.known_eda_commands:
195
+ error(f'Unknown target {key=} in {self.caller_info},',
196
+ f' must be one of opencos.deps.defaults.{SUPPORTED_TARGET_TABLE_KEYS=}',
197
+ f' or an eda command: {self.known_eda_commands}')
198
+
199
+ if key == 'tags':
200
+ remaining_deps_list += self.process_tags()
201
+ elif key == 'defines':
202
+ self.process_defines()
203
+ elif key == 'incdirs':
204
+ self.process_incdirs()
205
+ elif key == 'top':
206
+ self.process_top()
207
+ elif key == 'args':
208
+ self.process_args()
209
+ elif key == 'commands':
210
+ self.process_commands()
211
+ elif key == 'reqs':
212
+ self.process_reqs()
213
+ elif key == 'deps':
214
+ remaining_deps_list += self.process_deps_return_discovered_deps()
215
+
216
+ # We return the list of deps that still need to be resolved (['full_path/some_target', ...])
217
+ return remaining_deps_list
218
+
219
+ def process_tags( # pylint: disable=too-many-statements,too-many-branches,too-many-locals
220
+ self
221
+ ) -> list:
222
+ '''Returns List of added deps, applies tags (dict w/ details, if any) to
223
+ self.command_design_ref.
224
+
225
+ Tags are only supported as a Table within a target. Current we only support:
226
+ 'args', 'replace-config-tools', 'additive-config-tools', 'with-tools', 'with-args'.
227
+ '''
228
+
229
+ deps_tags_enables = self.config.get('dep_tags_enables', {})
230
+ ret_deps_added_from_tags = []
231
+
232
+ entry_tags = {} # from yml table
233
+ entry_tags.update(self.deps_entry.get('tags', {}))
234
+ for tagname,value in entry_tags.items():
235
+ debug(f'process_tags(): {tagname=} in {self.caller_info}' \
236
+ + f' observed: {value=}')
237
+ assert isinstance(value, dict), \
238
+ f'{tagname=} {value=} value must be a dict for in {self.caller_info}'
239
+ tags_dict_to_apply = value.copy()
240
+
241
+ for key in value.keys():
242
+ if key not in SUPPORTED_TAG_KEYS:
243
+ self.error(f'{tagname=} in {self.caller_info}:',
244
+ f'has unsupported {key=} {SUPPORTED_TAG_KEYS=}')
245
+
246
+ enable_tags_matched = False
247
+ disable_tags_matched = False
248
+ if tagname in self.command_design_ref.args['enable-tags']:
249
+ # tagname was force enabled by --enable-tags=tagname.
250
+ debug(f'process_tags(): {tagname=} in {self.caller_info=}',
251
+ 'will be enabled, matched in --enable-tags:',
252
+ f'{self.command_design_ref.args["enable-tags"]}')
253
+ enable_tags_matched = True
254
+ if tagname in self.command_design_ref.args['disable-tags']:
255
+ # tagname was force disabled by --disable-tags=tagname.
256
+ debug(f'process_tags(): {tagname=} in {self.caller_info=}',
257
+ 'will be disabled, matched in disable-tags:',
258
+ f'{self.command_design_ref.args["disable-tags"]}')
259
+ disable_tags_matched = True
260
+
261
+
262
+ apply_tag_items_tools = False
263
+ apply_tag_items_with_args = False
264
+
265
+ tool = self.args.get('tool', None)
266
+
267
+ if disable_tags_matched or enable_tags_matched:
268
+ # skip checking with-tools or with-args, b/c we are already
269
+ # force matched by tagname from --enable-tags or --disable-tags.
270
+ pass
271
+ else:
272
+
273
+ with_tools = dep_str2list(value.get('with-tools', []))
274
+ if with_tools and not deps_tags_enables.get('with-tools', None):
275
+ with_tools = []
276
+ warning(f'{tagname=} in {self.caller_info}:',
277
+ ' skipped due to with-tools disabled.')
278
+
279
+ with_args = value.get('with-args', {})
280
+ if not isinstance(with_args, dict):
281
+ error(f'{tagname=} in {self.caller_info}:',
282
+ ' with-args must be a table (dict) of key-value pairs')
283
+ if with_args and not deps_tags_enables.get('with-args', None):
284
+ with_args = {}
285
+ warning(f'{tagname=} in {self.caller_info}:',
286
+ ' skipped due to with-args disabled.')
287
+
288
+ # check with-tools?
289
+ if not with_tools:
290
+ apply_tag_items_tools = True # no with-tools present
291
+ elif tool in with_tools:
292
+ apply_tag_items_tools = True # with-tools present and we matched.
293
+ else:
294
+ # Each item of with-tools can also be in the form
295
+ # {tool (str)}:{TOOL.tool_version (str)}
296
+ # this matches Tool.get_full_tool_and_versions()
297
+ if getattr(self.command_design_ref, 'get_full_tool_and_versions', None):
298
+ tool_full_version = self.command_design_ref.get_full_tool_and_versions()
299
+ if tool_full_version and tool_full_version in with_tools:
300
+ apply_tag_items_tools = True
301
+
302
+ # check with-args?
303
+ with_args_matched_list = []
304
+ for k,v in with_args.items():
305
+ with_args_matched_list.append(False)
306
+ if not apply_tag_items_tools:
307
+ # If we didn't previously match with-tools (if with-tools was present),
308
+ # then we may not match the args, b/c those are tool dependend in the
309
+ # Command handling class.
310
+ pass
311
+ elif k not in self.command_design_ref.args:
312
+ warning(f'{tagname=} in {self.caller_info}:',
313
+ f'with-args key {k} is not a valid arg for {tool=}')
314
+ elif not isinstance(v, type(self.command_design_ref.args[k])):
315
+ warning(f'{tagname=} in {self.caller_info}:',
316
+ f' with-args table key {k} value {v} (type {type(v)}) does not',
317
+ f' match type in args (type {self.command_design_ref.args[k]})')
318
+ elif self.command_design_ref.args[k] == v:
319
+ # set it as matched:
320
+ with_args_matched_list[-1] = True
321
+ debug(f'{tagname=} in {self.caller_info}:',
322
+ f' with-args table key {k} value {v} matched')
323
+ else:
324
+ debug(f'{tagname=} in {self.caller_info}:',
325
+ f'with-args table key {k} value {v} did not match args value: ',
326
+ f'{self.command_design_ref.args[k]}')
327
+
328
+ if not with_args_matched_list:
329
+ apply_tag_items_with_args = True # no with-args set
330
+ else:
331
+ apply_tag_items_with_args = all(with_args_matched_list)
332
+
333
+ # Did we match all with-tools and with-args?
334
+ if disable_tags_matched:
335
+ apply_tag_items = False
336
+ elif enable_tags_matched:
337
+ apply_tag_items = True
338
+ else:
339
+ apply_tag_items = apply_tag_items_tools and apply_tag_items_with_args
340
+
341
+ if not apply_tag_items:
342
+ debug(f'process_tags(): {tagname=} in {self.caller_info}',
343
+ f'skipped for {tool=}, {with_args=}, {with_args_matched_list=}')
344
+ elif apply_tag_items_tools or apply_tag_items_with_args:
345
+ debug(f'process_tags(): {tagname=} in {self.caller_info=}',
346
+ f'applying tags for {tool=}, {with_args=}, {with_args_matched_list=},',
347
+ f'{tags_dict_to_apply.keys()=}')
348
+
349
+
350
+ if apply_tag_items:
351
+ # We have matched something (with-tools, etc).
352
+ # apply these in the original order of the keys:
353
+ for key in tags_dict_to_apply.keys():
354
+
355
+ if key == 'defines':
356
+ # apply defines:
357
+ self.apply_defines(value.get('defines', {}))
358
+
359
+ elif key == 'incdirs':
360
+ # apply incdirs:
361
+ self.apply_incdirs(value.get('incdirs', []))
362
+
363
+ elif key == 'args':
364
+ # apply args
365
+ args_list = dep_str2list(value.get('args', []))
366
+ if args_list and not deps_tags_enables.get('args', None):
367
+ args_list = []
368
+ warning(f'{tagname=} in {self.caller_info=}:',
369
+ ' skipped args due to args disabled.')
370
+ if args_list:
371
+ # This will apply knowns args to the target dep:
372
+ info(f'{tagname=} in {self.caller_info=}:',
373
+ f'applying args b/c {with_tools=} for {args_list=}')
374
+ self.apply_args(args_list)
375
+
376
+ elif key == 'reqs':
377
+ reqs_list = deps_target_get_deps_list(entry=value,
378
+ default_key='reqs',
379
+ target_node=self.target_node,
380
+ deps_file=self.deps_file)
381
+ self.apply_reqs(reqs_list)
382
+
383
+ elif key == 'deps':
384
+
385
+ # apply deps (includes commands, stray +define+ +incdir+)
386
+ # treat the same way we treat self.process_deps_return_discovered_deps
387
+ deps_list = deps_target_get_deps_list(entry=value,
388
+ default_key='deps',
389
+ target_node=self.target_node,
390
+ deps_file=self.deps_file)
391
+ ret_deps_added_from_tags += self.get_remaining_and_apply_deps(deps_list)
392
+
393
+ # for replace-config-tools or additive-config-tools from tags, these don't need to
394
+ # handle in order of tags keys:
395
+
396
+ # apply replace-config-tools
397
+ # This will replace lists (compile-waivers).
398
+ tool_config = value.get('replace-config-tools', {}).get(tool, None)
399
+ if tool_config and not deps_tags_enables.get('replace-config-tools', None):
400
+ tool_config = None
401
+ warning(f'{tagname=} in {self.caller_info}:',
402
+ ' skipped replace-config-tools b/c it is disabled.')
403
+ if tool_config and isinstance(tool_config, dict):
404
+ # apply it to self.tool_config:
405
+ info(f'{tagname=} in {self.caller_info}:',
406
+ f'applying replace-config-tools for {tool=}: {tool_config}')
407
+ eda_config.merge_config(self.command_design_ref.tool_config, tool_config)
408
+ # Since we altered command_design_ref.tool_config, need to call update on it:
409
+ self.command_design_ref.update_tool_config()
410
+ debug(f'{tagname=} in {self.caller_info}:',
411
+ 'Updated {self.command_design_ref.tool_config=}')
412
+
413
+ # apply additive-config-tools
414
+ # This will append to lists (compile-waivers)
415
+ tool_config = value.get('additive-config-tools', {}).get(tool, None)
416
+ if tool_config and not deps_tags_enables.get('additive-config-tools', None):
417
+ tool_config = None
418
+ warning(f'{tagname=} in {self.caller_info}:',
419
+ ' skipped additive-config-tools b/c it is disabled.')
420
+ if tool_config and isinstance(tool_config, dict):
421
+ # apply it to self.tool_config:
422
+ info(f'{tagname=} in {self.caller_info}:',
423
+ f'applying additive-config-tools for {tool=}: {tool_config}')
424
+ eda_config.merge_config(self.command_design_ref.tool_config, tool_config,
425
+ additive_strategy=True)
426
+ # Since we altered command_design_ref.tool_config, need to call update on it:
427
+ self.command_design_ref.update_tool_config()
428
+ debug(f'{tagname=} in {self.caller_info}:',
429
+ f'Updated {self.command_design_ref.tool_config=}')
430
+
431
+ return ret_deps_added_from_tags
432
+
433
+
434
+ def process_defines(self):
435
+ '''Returns None, applies defines (dict, if any) from self.deps_entry to
436
+ self.command_design_ref.'''
437
+
438
+ # Defines:
439
+ # apply command specific defines, with higher priority than the a
440
+ # deps_entry['sim']['defines'] entry,
441
+ # do this with dict1.update(dict2):
442
+ entry_defines = {}
443
+ entry_defines.update(self.deps_entry.get('defines', {}))
444
+ entry_defines.update(self.entry_eda_command.get('defines', {}))
445
+ assert isinstance(entry_defines, dict), \
446
+ f'{entry_defines=} for in {self.caller_info} must be a dict'
447
+
448
+ self.apply_defines(entry_defines)
449
+
450
+ def process_incdirs(self) -> None:
451
+ '''Returns None, applies incdirs (dict, if any) from self.deps_entry to
452
+ self.command_design_ref.'''
453
+
454
+ entry_incdirs = []
455
+ # apply command specific incdirs, higher in the incdir list:
456
+ entry_incdirs = dep_str2list(self.entry_eda_command.get('incdirs', []))
457
+ entry_incdirs += dep_str2list(self.deps_entry.get('incdirs', []))
458
+ assert isinstance(entry_incdirs, list), \
459
+ f'{entry_incdirs=} for in {self.caller_info} must be a list'
460
+ self.apply_incdirs(entry_incdirs)
461
+
462
+ def process_top(self) -> None:
463
+ '''Returns None, applies top (str, if any) from self.deps_entry to
464
+ self.command_design_ref.'''
465
+
466
+ if self.args['top'] != '':
467
+ return # already set
468
+
469
+ # For 'top', we overwrite it if not yet set.
470
+ # the command specific 'top' has higher priority.
471
+ entry_top = self.entry_eda_command.get('top', '') # if someone set target['sim']['top']
472
+ if entry_top == '':
473
+ entry_top = self.deps_entry.get('top', '') # if this target has target['top'] set
474
+
475
+ if entry_top != '':
476
+ if self.args['top'] == '':
477
+ # overwrite only if unset - we don't want other deps overriding the topmost
478
+ # target's setting for 'top'.
479
+ self.set_arg('top', str(entry_top))
480
+
481
+ def process_args(self) -> None:
482
+ '''Returns None, applies args (list or str, if any) from self.deps_entry to
483
+ self.command_design_ref.'''
484
+
485
+ # for 'args', process each. command specific args take higher priority that target args.
486
+ # run_argparser_on_list: uses argparse, which takes precedence on the last arg that is set,
487
+ # so put the command specific args last.
488
+ # Note that if an arg is already set, we do NOT update it
489
+ args_list = dep_str2list(self.deps_entry.get('args', []))
490
+ args_list += dep_str2list(self.entry_eda_command.get('args', []))
491
+
492
+ # for args_list, re-parse these args to apply them to self.args.
493
+ if not args_list:
494
+ return
495
+
496
+ debug(f'in {self.caller_info}: {args_list=}')
497
+ self.apply_args(args_list)
498
+
499
+ # TODO(drew): Currently, I can't support changing the 'config' via an arg encountered in
500
+ # DEPS.yml. This is prevented b/c --config-yml appears as a modifed arg no matter what
501
+ # (and we don't let DEPS.yml override modifed args, otherwise a target would override the
502
+ # user command line).
503
+
504
+
505
+ def get_commands( # pylint: disable=dangerous-default-value
506
+ self, commands: list = [], dep: str = ''
507
+ ) -> (list, list):
508
+ '''Returns tuple of (shell_commands_list, work_dir_add_srcs_list).
509
+
510
+ Does not have side effects on self.command_design_ref.
511
+ '''
512
+
513
+ default_ret = [], []
514
+
515
+ if not commands:
516
+ # if we weren't passed commands, then get them from our target (self.deps_entry)
517
+ commands = self.deps_entry.get('commands', [])
518
+
519
+ assert isinstance(commands, list), f'{self.deps_entry=} has {commands=} type is not list'
520
+
521
+ if not commands: # No commands in this target
522
+ return default_ret
523
+
524
+ debug(f"Got {self.deps_entry=} for in {self.caller_info}, has {commands=}")
525
+ shell_commands_list = [] # list of dicts
526
+ work_dir_add_srcs_list = [] # list of dicts
527
+
528
+ if not dep:
529
+ # if we weren't passed a dep, then use our target_node (str key for our self.deps_entry)
530
+ dep = self.target_node
531
+
532
+ # Run handler for this to convert to shell commands in self.command_design_ref
533
+ shell_commands_list, work_dir_add_srcs_list = deps_commands_handler(
534
+ config=self.command_design_ref.config,
535
+ eda_args=self.command_design_ref.args,
536
+ dep=dep,
537
+ deps_file=self.deps_file,
538
+ target_node=self.target_node,
539
+ target_path=self.target_path,
540
+ commands=commands
541
+ )
542
+
543
+ return shell_commands_list, work_dir_add_srcs_list
544
+
545
+ def process_commands( # pylint: disable=dangerous-default-value
546
+ self, commands: list = [], dep: str = ''
547
+ ) -> None:
548
+ '''Returns None, handles commands (shell, etc) in the target that aren' in the 'deps' list.
549
+
550
+ Applies these to self.command_design_ref.
551
+
552
+ You can optionally call this with a commands list and a single dep, which we support for
553
+ commands lists that exist within the 'deps' entry of a target.
554
+ '''
555
+
556
+ shell_commands_list, work_dir_add_srcs_list = self.get_commands(commands=commands, dep=dep)
557
+
558
+ # add these commands lists to self.command_design_ref:
559
+ # Process all shell_commands_list:
560
+ # This will track each shell command with its target_node and target_path
561
+ self.command_design_ref.append_shell_commands( cmds=shell_commands_list )
562
+ # Process all work_dir_add_srcs_list:
563
+ # This will track each added filename with its target_node and target_path
564
+ self.command_design_ref.append_work_dir_add_srcs( add_srcs=work_dir_add_srcs_list,
565
+ caller_info=self.caller_info )
566
+
567
+
568
+ def process_reqs(self) -> None:
569
+ '''Process any 'reqs:' table in a DEPS markup entry'''
570
+ reqs_list = deps_target_get_deps_list(entry=self.deps_entry,
571
+ default_key='reqs',
572
+ target_node=self.target_node,
573
+ deps_file=self.deps_file)
574
+ self.apply_reqs(reqs_list)
575
+
576
+
577
+ def process_deps_return_discovered_deps(self) -> list:
578
+ '''Returns list of deps targets to continue processing,
579
+
580
+ -- iterates through 'deps' for this target (self.deps_entry['deps'])
581
+ -- applies to self.command_design_ref
582
+ '''
583
+
584
+ # Get the list of deps from this entry (entry is a target in our DEPS.yml):
585
+ deps = deps_target_get_deps_list(
586
+ self.deps_entry,
587
+ target_node=self.target_node,
588
+ deps_file=self.deps_file
589
+ )
590
+ return self.get_remaining_and_apply_deps(deps)
591
+
592
+ def get_remaining_and_apply_deps(self, deps:list) -> list:
593
+ '''Given a list of deps, process what is supported in a "deps:" table in DEPS
594
+ markup file.'''
595
+
596
+ deps_targets_to_resolve = []
597
+
598
+ # Process deps (list)
599
+ for dep in deps:
600
+
601
+ typ = type(dep)
602
+ if typ not in SUPPORTED_DEP_KEYS_BY_TYPE:
603
+ self.error(f'{self.target_node=} {dep=} in {self.deps_file=}:' \
604
+ + f'has unsupported {type(dep)=} {SUPPORTED_DEP_KEYS_BY_TYPE=}')
605
+
606
+ for supported_values in SUPPORTED_DEP_KEYS_BY_TYPE.values():
607
+ if '*' in supported_values:
608
+ continue
609
+ if typ in [dict,list] and any(k not in supported_values for k in dep):
610
+ self.error(
611
+ f'{self.target_node=} {dep=} in {self.deps_file=}: has dict-key or',
612
+ f'list-item not in {SUPPORTED_DEP_KEYS_BY_TYPE[typ]=}'
613
+ )
614
+
615
+ # In-line commands in the deps list, in case the results need to be in strict file
616
+ # order for other deps
617
+ if isinstance(dep, dict) and 'commands' in dep:
618
+
619
+ commands = dep['commands']
620
+ debug(f"Got commands {dep=} for in {self.caller_info}, {commands=}")
621
+
622
+ assert isinstance(commands, list), \
623
+ f'dep commands must be a list: {dep=} in {self.caller_info}'
624
+
625
+ # For this, we need to get the returned commands (to keep strict order w/ other
626
+ # deps)
627
+ command_tuple = self.get_commands( commands=commands, dep=dep )
628
+ # TODO(drew): it might be cleaner to return a dict instead of list, b/c those
629
+ # are also ordered and we can pass type information, something like:
630
+ deps_targets_to_resolve.append(command_tuple)
631
+
632
+
633
+ elif isinstance(dep, str) and any(dep.startswith(x) for x in ['+define+', '+incdir']):
634
+ # Note: we still support +define+ and +incdir in the deps list.
635
+ # check for compile-time Verilog style plusarg, which are supported under targets
636
+ # These are not run-time Verilog style plusargs comsumable from within the .sv:
637
+ debug(f"Got plusarg (define, incdir) {dep=} for {self.caller_info}")
638
+ self.command_design_ref.process_plusarg(plusarg=dep, pwd=self.target_path)
639
+
640
+ else:
641
+ # If we made it this far, dep better be a str type.
642
+ assert isinstance(dep, str), f'{dep=} {type(dep)=} must be str'
643
+ dep_path = os.path.join(self.target_path, dep)
644
+ debug(f"Got dep {dep_path=} for in {self.caller_info}")
645
+
646
+ if dep_path in self.command_design_ref.targets_dict or \
647
+ dep_path in deps_targets_to_resolve:
648
+ debug(" - already processed, skipping")
649
+ else:
650
+ file_exists, _, _ = files.get_source_file(dep_path)
651
+ if file_exists:
652
+ debug(" - raw file, adding to return list...")
653
+ deps_targets_to_resolve.append(dep_path) # append, keeping file order.
654
+ else:
655
+ debug(" - a target (not a file) needing to be resolved, adding to return",
656
+ "list...")
657
+ deps_targets_to_resolve.append(dep_path) # append, keeping file order.
658
+
659
+ # We return the list of deps or files that still need to be resolved
660
+ # (['full_path/some_target', ...])
661
+ # items in this list are either:
662
+ # -- string (dep or file)
663
+ # -- tuple (unprocessed commands, in form: (shell_commands_list, work_dir_add_srcs_list))
664
+ # TODO(drew): it might be cleaner to return a dict instead of list, b/c those are also
665
+ # ordered and we can pass type information, something like:
666
+ # { dep1: 'file',
667
+ # dep2: 'target',
668
+ # dep3: 'command_tuple',
669
+ # }
670
+ return deps_targets_to_resolve