opencos-eda 0.2.48__py3-none-any.whl → 0.2.50__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.
- opencos/__init__.py +4 -2
- opencos/_version.py +10 -7
- opencos/commands/flist.py +8 -7
- opencos/commands/multi.py +14 -15
- opencos/commands/sim.py +5 -0
- opencos/commands/sweep.py +3 -2
- opencos/deps/__init__.py +0 -0
- opencos/deps/defaults.py +69 -0
- opencos/deps/deps_commands.py +419 -0
- opencos/deps/deps_file.py +326 -0
- opencos/deps/deps_processor.py +670 -0
- opencos/deps_schema.py +7 -8
- opencos/eda.py +84 -64
- opencos/eda_base.py +585 -316
- opencos/eda_config.py +85 -14
- opencos/eda_config_defaults.yml +36 -4
- opencos/eda_extract_targets.py +22 -14
- opencos/eda_tool_helper.py +33 -7
- opencos/export_helper.py +166 -86
- opencos/export_json_convert.py +31 -23
- opencos/files.py +2 -1
- opencos/hw/__init__.py +0 -0
- opencos/{oc_cli.py → hw/oc_cli.py} +9 -4
- opencos/names.py +0 -4
- opencos/peakrdl_cleanup.py +13 -7
- opencos/seed.py +19 -11
- opencos/tests/helpers.py +3 -2
- opencos/tests/test_deps_helpers.py +35 -32
- opencos/tests/test_eda.py +36 -29
- opencos/tests/test_eda_elab.py +7 -4
- opencos/tests/test_eda_synth.py +1 -1
- opencos/tests/test_oc_cli.py +1 -1
- opencos/tests/test_tools.py +4 -2
- opencos/tools/iverilog.py +2 -2
- opencos/tools/modelsim_ase.py +24 -2
- opencos/tools/questa.py +5 -3
- opencos/tools/questa_fse.py +57 -0
- opencos/tools/riviera.py +1 -1
- opencos/tools/slang.py +9 -3
- opencos/tools/surelog.py +1 -1
- opencos/tools/verilator.py +26 -1
- opencos/tools/vivado.py +34 -27
- opencos/tools/yosys.py +4 -3
- opencos/util.py +532 -474
- opencos/utils/__init__.py +0 -0
- opencos/utils/markup_helpers.py +98 -0
- opencos/utils/str_helpers.py +111 -0
- opencos/utils/subprocess_helpers.py +108 -0
- {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.50.dist-info}/METADATA +1 -1
- opencos_eda-0.2.50.dist-info/RECORD +89 -0
- {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.50.dist-info}/entry_points.txt +1 -1
- opencos/deps_helpers.py +0 -1346
- opencos_eda-0.2.48.dist-info/RECORD +0 -79
- /opencos/{pcie.py → hw/pcie.py} +0 -0
- {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.50.dist-info}/WHEEL +0 -0
- {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.50.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.50.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.50.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
|