opencos-eda 0.2.31__py3-none-any.whl → 0.2.33__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/commands/multi.py +26 -37
- opencos/commands/sweep.py +140 -80
- opencos/deps_helpers.py +3 -2
- opencos/deps_schema.py +40 -11
- opencos/eda_base.py +64 -11
- opencos/peakrdl_cleanup.py +0 -1
- opencos/tests/helpers.py +34 -10
- opencos/tests/test_deps_helpers.py +6 -5
- opencos/tests/test_eda.py +65 -41
- opencos/tests/test_tools.py +11 -2
- opencos/util.py +2 -1
- {opencos_eda-0.2.31.dist-info → opencos_eda-0.2.33.dist-info}/METADATA +1 -1
- {opencos_eda-0.2.31.dist-info → opencos_eda-0.2.33.dist-info}/RECORD +18 -18
- {opencos_eda-0.2.31.dist-info → opencos_eda-0.2.33.dist-info}/WHEEL +0 -0
- {opencos_eda-0.2.31.dist-info → opencos_eda-0.2.33.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.2.31.dist-info → opencos_eda-0.2.33.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.2.31.dist-info → opencos_eda-0.2.33.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.2.31.dist-info → opencos_eda-0.2.33.dist-info}/top_level.txt +0 -0
opencos/commands/multi.py
CHANGED
|
@@ -268,26 +268,23 @@ class CommandMulti(CommandParallel):
|
|
|
268
268
|
if parsed.parallel < 1 or parsed.parallel > 256:
|
|
269
269
|
self.error("Arg 'parallel' must be between 1 and 256")
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
if value in self.config['command_handler'].keys():
|
|
273
|
-
command = value
|
|
274
|
-
unparsed.remove(value)
|
|
275
|
-
break
|
|
271
|
+
command = self.get_command_from_unparsed_args(tokens=unparsed)
|
|
276
272
|
|
|
277
273
|
# Need to know the tool for this command, either it was set correctly via --tool and/or
|
|
278
274
|
# the command (class) will tell us.
|
|
279
275
|
all_multi_tools = self.multi_which_tools(command)
|
|
280
276
|
|
|
277
|
+
single_cmd_unparsed = self.get_unparsed_args_on_single_command(
|
|
278
|
+
command=command, tokens=unparsed
|
|
279
|
+
)
|
|
280
|
+
|
|
281
281
|
util.debug(f"Multi: {unparsed=}, looking for target_globs")
|
|
282
282
|
for token in unparsed:
|
|
283
|
-
if token
|
|
284
|
-
# save all --arg, -arg, or +plusarg for the job target:
|
|
285
|
-
arg_tokens.append(token)
|
|
286
|
-
else:
|
|
283
|
+
if token in single_cmd_unparsed:
|
|
287
284
|
target_globs.append(token)
|
|
285
|
+
else:
|
|
286
|
+
arg_tokens.append(token)
|
|
288
287
|
|
|
289
|
-
if command == "":
|
|
290
|
-
self.error("Didn't get a command after 'multi'!")
|
|
291
288
|
|
|
292
289
|
# now we need to expand the target list
|
|
293
290
|
self.single_command = command
|
|
@@ -379,27 +376,23 @@ class CommandMulti(CommandParallel):
|
|
|
379
376
|
# Do not use for CommandMulti, b/c we support list of tools.
|
|
380
377
|
raise NotImplementedError
|
|
381
378
|
|
|
382
|
-
def multi_which_tools(self, command):
|
|
379
|
+
def multi_which_tools(self, command) -> list:
|
|
383
380
|
'''returns a list, or None, of the tool that was already determined to run the command
|
|
384
381
|
|
|
385
382
|
CommandToolsMulti will override and return its own list'''
|
|
386
383
|
return [eda_base.which_tool(command, config=self.config)]
|
|
387
384
|
|
|
388
|
-
def _append_job_command_args(
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if
|
|
398
|
-
|
|
399
|
-
if cfg_yml_fname:
|
|
400
|
-
command_list.append(f'--config-yml={cfg_yml_fname}')
|
|
401
|
-
if '--eda-safe' in self.config['eda_original_args']:
|
|
402
|
-
command_list.append('--eda-safe')
|
|
385
|
+
def _append_job_command_args( # pylint: disable=R0913,R0917 # too-many-arguments
|
|
386
|
+
self, command_list: list, tool: str, all_multi_tools: list, short_target: str,
|
|
387
|
+
command: str
|
|
388
|
+
) -> None:
|
|
389
|
+
|
|
390
|
+
super().update_args_list(args=command_list, tool=tool)
|
|
391
|
+
if self.args.get('export-jsonl', False):
|
|
392
|
+
# Special case for 'multi' --export-jsonl, run reach child with --export-json
|
|
393
|
+
command_list.append('--export-json')
|
|
394
|
+
if tool and len(all_multi_tools) > 1:
|
|
395
|
+
command_list.append(f'--sub-work-dir={short_target}.{command}.{tool}')
|
|
403
396
|
|
|
404
397
|
def append_jobs_from_targets(self, args:list):
|
|
405
398
|
'''Helper method in CommandMulti to apply 'args' (list) to all self.targets,
|
|
@@ -422,17 +415,11 @@ class CommandMulti(CommandParallel):
|
|
|
422
415
|
|
|
423
416
|
_, short_target = os.path.split(target) # trim path info on left
|
|
424
417
|
|
|
425
|
-
self._append_job_command_args(
|
|
418
|
+
self._append_job_command_args(
|
|
419
|
+
command_list=command_list, tool=tool, all_multi_tools=all_multi_tools,
|
|
420
|
+
short_target=short_target, command=command
|
|
421
|
+
)
|
|
426
422
|
|
|
427
|
-
if tool:
|
|
428
|
-
# tool can be None, we won't add it to the command (assumes default from config-yml)
|
|
429
|
-
command_list.append('--tool=' + tool)
|
|
430
|
-
if len(all_multi_tools) > 1:
|
|
431
|
-
command_list += [f'--sub-work-dir={short_target}.{command}.{tool}']
|
|
432
|
-
|
|
433
|
-
if self.args.get('export-jsonl', False):
|
|
434
|
-
# Special case for 'multi' --export-jsonl, run reach child with --export-json
|
|
435
|
-
command_list += [ '--export-json']
|
|
436
423
|
# if self.args['parallel']: command_list += ['--quiet']
|
|
437
424
|
command_list += args # put the args prior to the target.
|
|
438
425
|
command_list += [target]
|
|
@@ -540,6 +527,8 @@ class CommandMulti(CommandParallel):
|
|
|
540
527
|
output_json_path=output_json_path)
|
|
541
528
|
|
|
542
529
|
|
|
530
|
+
|
|
531
|
+
|
|
543
532
|
class CommandToolsMulti(CommandMulti):
|
|
544
533
|
'''eda.py command handler for: eda tools-multi <args,targets,target-globs,...>
|
|
545
534
|
|
opencos/commands/sweep.py
CHANGED
|
@@ -7,7 +7,7 @@ import os
|
|
|
7
7
|
import re
|
|
8
8
|
|
|
9
9
|
from opencos import util
|
|
10
|
-
from opencos.eda_base import CommandDesign, CommandParallel, get_eda_exec
|
|
10
|
+
from opencos.eda_base import CommandDesign, CommandParallel, get_eda_exec, which_tool
|
|
11
11
|
|
|
12
12
|
class CommandSweep(CommandDesign, CommandParallel):
|
|
13
13
|
'''Command handler for: eda sweep ...'''
|
|
@@ -19,6 +19,19 @@ class CommandSweep(CommandDesign, CommandParallel):
|
|
|
19
19
|
CommandParallel.__init__(self, config=config)
|
|
20
20
|
self.sweep_target = ''
|
|
21
21
|
self.single_command = ''
|
|
22
|
+
self.args.update({
|
|
23
|
+
'sweep': []
|
|
24
|
+
})
|
|
25
|
+
self.args_help.update({
|
|
26
|
+
"sweep": ("List append arg, where range or value expansion is peformed on the RHS:"
|
|
27
|
+
" --sweep='--arg0=(start,last,iter=1)' "
|
|
28
|
+
" --sweep='--arg1=[val0,val1,val2,...]' "
|
|
29
|
+
" --sweep='+define+NAME0=(start,last,iter=1)' "
|
|
30
|
+
" --sweep='+define+NAME1=[val0,val1,val2,...]' "
|
|
31
|
+
" --sweep='+define+[NAME0,NAME1,NAME2,...]' ."
|
|
32
|
+
" Note that range expansion of (1,4) will expand to values [1,2,3,4]."
|
|
33
|
+
),
|
|
34
|
+
})
|
|
22
35
|
|
|
23
36
|
|
|
24
37
|
def check_args(self) -> None:
|
|
@@ -27,22 +40,9 @@ class CommandSweep(CommandDesign, CommandParallel):
|
|
|
27
40
|
self.error(f"Arg {self.args['parallel']=} must be between 1 and 256")
|
|
28
41
|
|
|
29
42
|
def _append_sweep_args(self, arg_tokens: list) -> None:
|
|
30
|
-
'''Modifies list arg_tokens,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
# CommandMulti has we need to pass global args to each sweep job, which we can
|
|
34
|
-
# do via arg_tokens (list)
|
|
35
|
-
# TODO(drew): fix this, for now it works but --color and other args do not work.
|
|
36
|
-
if any(a.startswith('--config-yml') for a in self.config['eda_original_args']):
|
|
37
|
-
cfg_yml_fname = self.config.get('config-yml', None)
|
|
38
|
-
if cfg_yml_fname:
|
|
39
|
-
arg_tokens.append(f'--config-yml={cfg_yml_fname}')
|
|
40
|
-
if '--eda-safe' in self.config['eda_original_args']:
|
|
41
|
-
arg_tokens.append('--eda-safe')
|
|
42
|
-
if any(a.startswith('--tool') for a in self.config['eda_original_args']):
|
|
43
|
-
tool = self.config.get('tool', None)
|
|
44
|
-
if tool:
|
|
45
|
-
arg_tokens.append('--tool=' + tool)
|
|
43
|
+
'''Modifies list arg_tokens, using known top-level args'''
|
|
44
|
+
tool = which_tool(command=self.single_command, config=self.config)
|
|
45
|
+
super().update_args_list(args=arg_tokens, tool=tool)
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def process_tokens(
|
|
@@ -55,7 +55,7 @@ class CommandSweep(CommandDesign, CommandParallel):
|
|
|
55
55
|
- builds sweep_axis_list to run multiple jobs for the target
|
|
56
56
|
'''
|
|
57
57
|
|
|
58
|
-
#
|
|
58
|
+
# 'sweep' is special in the way it handles tokens, due to most of them being processed by
|
|
59
59
|
# a sub instance
|
|
60
60
|
sweep_axis_list = []
|
|
61
61
|
arg_tokens = []
|
|
@@ -64,59 +64,49 @@ class CommandSweep(CommandDesign, CommandParallel):
|
|
|
64
64
|
tokens=tokens,
|
|
65
65
|
parser_arg_list=[
|
|
66
66
|
'parallel',
|
|
67
|
+
'sweep',
|
|
67
68
|
],
|
|
68
69
|
apply_parsed_args=True
|
|
69
70
|
)
|
|
70
71
|
|
|
71
72
|
self.check_args()
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
self.single_command = self.get_command_from_unparsed_args(tokens=tokens)
|
|
74
|
+
self.single_command = self.get_command_from_unparsed_args(tokens=unparsed)
|
|
76
75
|
|
|
77
76
|
self._append_sweep_args(arg_tokens=arg_tokens)
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
78
|
+
for sweep_arg_value in self.args['sweep']:
|
|
79
|
+
# Deal with --sweep= args we already parsed, but haven't expanded yet.
|
|
80
|
+
sweep_arg_value = util.strip_outer_quotes(sweep_arg_value)
|
|
81
|
+
sweep_axis_list_entry = self._process_sweep_arg(sweep_arg_value=sweep_arg_value)
|
|
82
|
+
if sweep_axis_list_entry:
|
|
83
|
+
sweep_axis_list.append(sweep_axis_list_entry)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# command, --parallel, and --sweep already processed by argparse,
|
|
87
|
+
# let's tentatively parse what the child jobs cannot consume.
|
|
88
|
+
# Whatever is leftover is either an eda target, or another unparsed token.
|
|
89
|
+
single_cmd_unparsed = self.get_unparsed_args_on_single_command(
|
|
90
|
+
command=self.single_command, tokens=unparsed
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
for token in unparsed:
|
|
94
|
+
if token in single_cmd_unparsed:
|
|
95
|
+
|
|
96
|
+
if self.resolve_target(token, no_recursion=True):
|
|
97
|
+
if self.sweep_target:
|
|
98
|
+
self.error("Sweep can only take one target, already got",
|
|
99
|
+
f"{self.sweep_target} now getting {token}")
|
|
100
|
+
self.sweep_target = token
|
|
101
|
+
else:
|
|
102
|
+
# If we don't know what to do with it, pass it to downstream
|
|
103
|
+
arg_tokens.append(token)
|
|
104
|
+
|
|
105
|
+
else:
|
|
106
|
+
# If it wasn't in single_cmd_unparsed, then it can definitely be
|
|
107
|
+
# consumed downstream
|
|
109
108
|
arg_tokens.append(token)
|
|
110
|
-
|
|
111
|
-
if self.resolve_target(token, no_recursion=True):
|
|
112
|
-
if self.sweep_target != "":
|
|
113
|
-
self.error(f"Sweep can only take one target, already got {self.sweep_target},"
|
|
114
|
-
f"now getting {token}")
|
|
115
|
-
self.sweep_target = token
|
|
116
|
-
continue
|
|
117
|
-
self.error(f"Sweep doesn't know what to do with arg '{token}'")
|
|
118
|
-
if self.single_command == "":
|
|
119
|
-
self.error("Didn't get a command after 'sweep'!")
|
|
109
|
+
|
|
120
110
|
|
|
121
111
|
# now we need to expand the target list
|
|
122
112
|
util.debug(f"Sweep: command: '{self.single_command}'")
|
|
@@ -128,7 +118,74 @@ class CommandSweep(CommandDesign, CommandParallel):
|
|
|
128
118
|
|
|
129
119
|
self.expand_sweep_axis(arg_tokens=arg_tokens, sweep_axis_list=sweep_axis_list)
|
|
130
120
|
self.run_jobs(command=self.single_command)
|
|
131
|
-
return
|
|
121
|
+
return [] # we used all of unparsed.
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def _process_sweep_arg(sweep_arg_value: str) -> dict:
|
|
126
|
+
'''If processed, returns a non-empty dict. Handles processing of --sweep=VALUE
|
|
127
|
+
|
|
128
|
+
Where VALUE could be one of:
|
|
129
|
+
--arg0=(1,4)
|
|
130
|
+
--arg1=[val0,val1]
|
|
131
|
+
+define+NAME0=[val0,val1]
|
|
132
|
+
|
|
133
|
+
Return value is {} or {'lhs': str, 'operator': str (+ or =), 'values': list}
|
|
134
|
+
'''
|
|
135
|
+
|
|
136
|
+
sweep_arg_value = util.strip_outer_quotes(sweep_arg_value)
|
|
137
|
+
|
|
138
|
+
util.debug(f'{sweep_arg_value=}')
|
|
139
|
+
# Try to match a sweep range expansion:
|
|
140
|
+
# --sweep='--arg0=(1,4)'
|
|
141
|
+
# --sweep='--arg0=(1,4,1)'
|
|
142
|
+
# --sweep='+define+NAME0=(1,4,1)'
|
|
143
|
+
m = re.match(
|
|
144
|
+
r'(.*)(\=) \( ([\d\.]+) \, ([\d\.]+) (\, ([\d\.]+) )? \)'.replace(' ',''),
|
|
145
|
+
sweep_arg_value
|
|
146
|
+
)
|
|
147
|
+
if m:
|
|
148
|
+
lhs = m.group(1)
|
|
149
|
+
operator = m.group(2)
|
|
150
|
+
sweep_axis_values = []
|
|
151
|
+
if m.group(5):
|
|
152
|
+
rhs_range_iter = int(m.group(6))
|
|
153
|
+
else:
|
|
154
|
+
rhs_range_iter = 1
|
|
155
|
+
for v in range(int(m.group(3)), int(m.group(4)) + 1, rhs_range_iter):
|
|
156
|
+
sweep_axis_values.append(v)
|
|
157
|
+
util.debug(f"Sweep axis: {lhs} {operator} {sweep_axis_values}")
|
|
158
|
+
return {
|
|
159
|
+
'lhs': lhs,
|
|
160
|
+
'operator': operator,
|
|
161
|
+
'values': sweep_axis_values,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Try to match a sweep list value expansion:
|
|
165
|
+
# --sweep='--arg0=[1,2]'
|
|
166
|
+
# --sweep='--arg1=[3,4,5,6]'
|
|
167
|
+
# --sweep='+define+NAME=[8,9]'
|
|
168
|
+
# --sweep='+define+[NAME0,NAME1,NAME2]'
|
|
169
|
+
# --sweep='+define+[NAME0=1,NAME1=22,NAME2=46]'
|
|
170
|
+
m = re.match(
|
|
171
|
+
r'(.*)([\=\+]) \[ ([^\]]+) \] '.replace(' ',''),
|
|
172
|
+
sweep_arg_value
|
|
173
|
+
)
|
|
174
|
+
if m:
|
|
175
|
+
sweep_axis_values = []
|
|
176
|
+
lhs = m.group(1)
|
|
177
|
+
operator = m.group(2)
|
|
178
|
+
for v in m.group(3).split(','):
|
|
179
|
+
sweep_axis_values.append(v)
|
|
180
|
+
util.debug(f"Sweep axis: {lhs} {operator} {sweep_axis_values}")
|
|
181
|
+
return {
|
|
182
|
+
'lhs': lhs,
|
|
183
|
+
'operator': operator,
|
|
184
|
+
'values': sweep_axis_values,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
util.warning(f'Ignored unprocessed expansion for --sweep={sweep_arg_value}')
|
|
188
|
+
return {}
|
|
132
189
|
|
|
133
190
|
|
|
134
191
|
def expand_sweep_axis(
|
|
@@ -136,35 +193,38 @@ class CommandSweep(CommandDesign, CommandParallel):
|
|
|
136
193
|
) -> None:
|
|
137
194
|
'''Returns None, appends jobs to self.jobs to be run by CommandParallel.run_jobs(..)'''
|
|
138
195
|
|
|
139
|
-
command
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
f"arg_tokens={arg_tokens}, sweep_axis_list={sweep_axis_list}")
|
|
144
|
-
if len(sweep_axis_list) == 0:
|
|
196
|
+
util.debug(f"Entering expand_sweep_axis: command={self.single_command},",
|
|
197
|
+
f"target={self.sweep_target}, arg_tokens={arg_tokens},",
|
|
198
|
+
f"sweep_axis_list={sweep_axis_list}")
|
|
199
|
+
if not sweep_axis_list:
|
|
145
200
|
# we aren't sweeping anything, create one job
|
|
146
|
-
snapshot_name =
|
|
201
|
+
snapshot_name = self.sweep_target.replace('../','').replace('/','_') + sweep_string
|
|
147
202
|
eda_path = get_eda_exec('sweep')
|
|
148
203
|
self.jobs.append({
|
|
149
204
|
'name' : snapshot_name,
|
|
150
205
|
'index' : len(self.jobs),
|
|
151
206
|
'command_list' : (
|
|
152
|
-
[eda_path,
|
|
207
|
+
[eda_path, self.single_command, self.sweep_target,
|
|
208
|
+
'--job_name', snapshot_name] + arg_tokens
|
|
153
209
|
)
|
|
154
210
|
})
|
|
155
211
|
return
|
|
156
|
-
|
|
212
|
+
|
|
213
|
+
sweep_axis = sweep_axis_list.pop(0)
|
|
214
|
+
lhs = sweep_axis['lhs']
|
|
215
|
+
operator = sweep_axis['operator']
|
|
216
|
+
|
|
217
|
+
lhs_trimmed = lhs.replace('-', '').replace('+', '').replace('=', '')
|
|
218
|
+
|
|
157
219
|
for v in sweep_axis['values']:
|
|
158
|
-
this_arg_tokens =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this_arg_tokens.append(a_swept)
|
|
162
|
-
next_sweep_axis_list = []
|
|
163
|
-
if len(sweep_axis_list)>1:
|
|
164
|
-
next_sweep_axis_list = sweep_axis_list[1:]
|
|
220
|
+
this_arg_tokens = arg_tokens.copy()
|
|
221
|
+
this_arg_tokens.append(f'{lhs}{operator}{v}')
|
|
222
|
+
|
|
165
223
|
v_string = f"{v}".replace('.','p')
|
|
224
|
+
this_sweep_string = sweep_string + f"_{lhs_trimmed}_{v_string}"
|
|
225
|
+
|
|
166
226
|
self.expand_sweep_axis(
|
|
167
227
|
arg_tokens=this_arg_tokens,
|
|
168
|
-
sweep_axis_list=
|
|
169
|
-
sweep_string
|
|
228
|
+
sweep_axis_list=sweep_axis_list,
|
|
229
|
+
sweep_string=this_sweep_string
|
|
170
230
|
)
|
opencos/deps_helpers.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import fnmatch
|
|
3
3
|
import os
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
import sys
|
|
5
6
|
import re
|
|
6
7
|
import shutil
|
|
@@ -1149,9 +1150,9 @@ def parse_deps_peakrdl(line : str, target_path : str, target_node : str, enable
|
|
|
1149
1150
|
|
|
1150
1151
|
|
|
1151
1152
|
shell_commands = [
|
|
1152
|
-
[ 'peakrdl', 'regblock', '-o', 'peakrdl/'] + args_list,
|
|
1153
|
+
[ 'peakrdl', 'regblock', '-o', str(Path('peakrdl/'))] + args_list,
|
|
1153
1154
|
# Edit file to apply some verilator waivers, etc, from peakrdl_cleanup.py:
|
|
1154
|
-
[ 'python3', peakrdl_cleanup_py, f'peakrdl/{top}.sv', f'peakrdl/{top}.sv' ],
|
|
1155
|
+
[ 'python3', peakrdl_cleanup_py, str(Path(f'peakrdl/{top}.sv')), str(Path(f'peakrdl/{top}.sv')) ],
|
|
1155
1156
|
]
|
|
1156
1157
|
|
|
1157
1158
|
ret_dict = {
|
opencos/deps_schema.py
CHANGED
|
@@ -125,7 +125,7 @@ import sys
|
|
|
125
125
|
|
|
126
126
|
from schema import Schema, Or, Optional, SchemaError
|
|
127
127
|
|
|
128
|
-
from opencos
|
|
128
|
+
from opencos import util, deps_helpers
|
|
129
129
|
|
|
130
130
|
# Because we deal with YAML, where a Table Key with dangling/empty value is allowed
|
|
131
131
|
# and we have things like SystemVerilog defines where there's a Table key with no Value,
|
|
@@ -299,8 +299,10 @@ FILE_SIMPLIFIED = Schema(
|
|
|
299
299
|
|
|
300
300
|
|
|
301
301
|
|
|
302
|
-
def check(data: dict, schema_obj=FILE):
|
|
302
|
+
def check(data: dict, schema_obj=FILE) -> (bool, str):
|
|
303
303
|
'''Returns (bool, str) for checking dict against FILE schema'''
|
|
304
|
+
|
|
305
|
+
|
|
304
306
|
try:
|
|
305
307
|
schema_obj.validate(data)
|
|
306
308
|
return True, None
|
|
@@ -310,6 +312,37 @@ def check(data: dict, schema_obj=FILE):
|
|
|
310
312
|
return False, str(e)
|
|
311
313
|
|
|
312
314
|
|
|
315
|
+
def deps_markup_safe_load(deps_filepath: str) -> (bool, dict):
|
|
316
|
+
'''Returns tuple (bool False if took errors, dict of markp data)'''
|
|
317
|
+
current_errors = util.args['errors']
|
|
318
|
+
data = deps_helpers.deps_markup_safe_load(deps_filepath)
|
|
319
|
+
if util.args['errors'] > current_errors:
|
|
320
|
+
return False, data
|
|
321
|
+
return True, data
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def check_file(filepath: str, schema_obj=FILE) -> (bool, str, str):
|
|
325
|
+
'''Returns tuple (bool pass/fail, str error retdata, str deps_filepath)'''
|
|
326
|
+
|
|
327
|
+
deps_filepath = filepath
|
|
328
|
+
if os.path.isdir(filepath):
|
|
329
|
+
deps_filepath = deps_helpers.get_deps_markup_file(base_path=filepath)
|
|
330
|
+
|
|
331
|
+
# get deps file
|
|
332
|
+
if not os.path.exists(deps_filepath):
|
|
333
|
+
print(f'ERROR: internal error(s) no DEPS.[yml|..] found in {filepath=}')
|
|
334
|
+
return False, '', deps_filepath
|
|
335
|
+
|
|
336
|
+
passes, data = deps_markup_safe_load(deps_filepath)
|
|
337
|
+
if not passes:
|
|
338
|
+
print(f'ERROR: internal error(s) from deps_markup_safe_load({deps_filepath=})')
|
|
339
|
+
return False, '', deps_filepath
|
|
340
|
+
|
|
341
|
+
passes, retdata = check(data, schema_obj)
|
|
342
|
+
return passes, retdata, deps_filepath
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
313
346
|
def check_files(files, schema_obj=FILE) -> bool:
|
|
314
347
|
'''Returns True if files lint cleanly in the FILE schema.'''
|
|
315
348
|
|
|
@@ -319,20 +352,16 @@ def check_files(files, schema_obj=FILE) -> bool:
|
|
|
319
352
|
passes_list = []
|
|
320
353
|
error_files = []
|
|
321
354
|
for filepath in files:
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
# get deps file
|
|
325
|
-
deps_filepath = get_deps_markup_file(base_path=filepath)
|
|
326
|
-
assert os.path.exists(deps_filepath)
|
|
327
|
-
data = deps_markup_safe_load(deps_filepath)
|
|
328
|
-
passes, retdata = check(data, schema_obj)
|
|
355
|
+
|
|
356
|
+
passes, retdata, deps_filepath = check_file(filepath, schema_obj)
|
|
329
357
|
passes_list.append(passes)
|
|
330
358
|
if passes:
|
|
331
359
|
print(f'{deps_filepath}: [PASS]')
|
|
332
360
|
if not passes:
|
|
333
361
|
print(f'ERROR: {deps_filepath}:')
|
|
334
|
-
|
|
335
|
-
|
|
362
|
+
if retdata:
|
|
363
|
+
print('-- retdata --')
|
|
364
|
+
print(retdata)
|
|
336
365
|
print(f' previous error on: {deps_filepath}\n')
|
|
337
366
|
error_files.append(deps_filepath)
|
|
338
367
|
|
opencos/eda_base.py
CHANGED
|
@@ -398,12 +398,6 @@ class Command:
|
|
|
398
398
|
else:
|
|
399
399
|
assert False, f'{key=} {value=} how do we do argparse for this type of value?'
|
|
400
400
|
|
|
401
|
-
# TODO(drew): it might be nice to support positional args here as a list
|
|
402
|
-
# self.target_args (files/targets/patterns), something like:
|
|
403
|
-
# parser.add_argument(
|
|
404
|
-
# 'targets', nargs='+', help='positional arg for targets/files/pattern'
|
|
405
|
-
# )
|
|
406
|
-
|
|
407
401
|
return parser
|
|
408
402
|
|
|
409
403
|
|
|
@@ -425,7 +419,7 @@ class Command:
|
|
|
425
419
|
parsed, unparsed = parser.parse_known_args(tokens + [''])
|
|
426
420
|
unparsed = list(filter(None, unparsed))
|
|
427
421
|
except argparse.ArgumentError:
|
|
428
|
-
self.error(f'problem attempting to parse_known_args for {tokens=}')
|
|
422
|
+
self.error(f'problem {command_name=} attempting to parse_known_args for {tokens=}')
|
|
429
423
|
|
|
430
424
|
parsed_as_dict = vars(parsed)
|
|
431
425
|
|
|
@@ -506,7 +500,8 @@ class Command:
|
|
|
506
500
|
break
|
|
507
501
|
|
|
508
502
|
if not ret and error_if_no_command:
|
|
509
|
-
self.error(f"Looking for a valid eda <command>
|
|
503
|
+
self.error(f"Looking for a valid eda {self.command_name} <command>",
|
|
504
|
+
f"but didn't find one in {tokens=}")
|
|
510
505
|
return ret
|
|
511
506
|
|
|
512
507
|
|
|
@@ -696,8 +691,12 @@ class CommandDesign(Command):
|
|
|
696
691
|
util.info(f'run_dep_shell_commands {iter=}: {d=}')
|
|
697
692
|
clist = util.ShellCommandList(d['exec_list'])
|
|
698
693
|
# NOTE(drew): shell=True subprocess call, can disable with self.config
|
|
699
|
-
|
|
700
|
-
|
|
694
|
+
# However, in Windows, we seem to have to run these with shell=False
|
|
695
|
+
if sys.platform.startswith('win'):
|
|
696
|
+
self.exec(self.args['work-dir'], clist, shell=False)
|
|
697
|
+
else:
|
|
698
|
+
self.exec(self.args['work-dir'], clist, tee_fpath=clist.tee_fpath,
|
|
699
|
+
shell=self.config.get('deps_subprocess_shell', False))
|
|
701
700
|
|
|
702
701
|
def update_file_lists_for_work_dir(self):
|
|
703
702
|
if len(self.dep_work_dir_add_srcs) == 0:
|
|
@@ -748,7 +747,7 @@ class CommandDesign(Command):
|
|
|
748
747
|
to unprocessed-plusargs.
|
|
749
748
|
'''
|
|
750
749
|
|
|
751
|
-
# Since this may be coming from a raw CLI/bash argparser, we may have
|
|
750
|
+
# Since this may be coming from a raw CLI/bash/powershell argparser, we may have
|
|
752
751
|
# args that come from shlex.quote(token), such as:
|
|
753
752
|
# token = '\'+define+OC_ROOT="/foo/bar/opencos"\''
|
|
754
753
|
# So we strip all outer ' or " on the plusarg:
|
|
@@ -1514,3 +1513,57 @@ class CommandParallel(Command):
|
|
|
1514
1513
|
if self.status == 0:
|
|
1515
1514
|
self.status = 0 if len(self.jobs_status) == 0 else max(self.jobs_status)
|
|
1516
1515
|
util.fancy_stop()
|
|
1516
|
+
|
|
1517
|
+
|
|
1518
|
+
def update_args_list(self, args: list, tool: str) -> None:
|
|
1519
|
+
'''Modfies list args, using allow-listed known top-level args:
|
|
1520
|
+
|
|
1521
|
+
--config-yml
|
|
1522
|
+
--eda-safe
|
|
1523
|
+
--tool
|
|
1524
|
+
|
|
1525
|
+
Many args were consumed by eda before CommandParallel saw them
|
|
1526
|
+
(for commands like 'multi' or 'sweep'). Some are in self.config.
|
|
1527
|
+
We need to apply those eda level args to each single exec-command
|
|
1528
|
+
'''
|
|
1529
|
+
if any(a.startswith('--config-yml') for a in self.config['eda_original_args']):
|
|
1530
|
+
cfg_yml_fname = self.config.get('config-yml', None)
|
|
1531
|
+
if cfg_yml_fname:
|
|
1532
|
+
args.append(f'--config-yml={cfg_yml_fname}')
|
|
1533
|
+
if '--eda-safe' in self.config['eda_original_args']:
|
|
1534
|
+
args.append('--eda-safe')
|
|
1535
|
+
if tool:
|
|
1536
|
+
# tool can be None, if so we won't add it to the command (assumes default from
|
|
1537
|
+
# config-yml auto load order)
|
|
1538
|
+
args.append('--tool=' + tool)
|
|
1539
|
+
|
|
1540
|
+
|
|
1541
|
+
def get_unparsed_args_on_single_command(self, command: str, tokens: list) -> list:
|
|
1542
|
+
'''Returns a list of args that the single (non-multi) command cannot parse
|
|
1543
|
+
|
|
1544
|
+
This will error on bad --args or -arg, such as:
|
|
1545
|
+
eda multi sim --seeeed=1
|
|
1546
|
+
is not a valid arg in CommandSim
|
|
1547
|
+
|
|
1548
|
+
+arg=value, +arg+value will not be included in the return list, those are
|
|
1549
|
+
intended to be consumed by the single/job command downstream (anything starting
|
|
1550
|
+
with '+')
|
|
1551
|
+
|
|
1552
|
+
Used by CommandMulti and CommandSweep.
|
|
1553
|
+
'''
|
|
1554
|
+
single_cmd_handler = self.config['command_handler'][command](config=self.config)
|
|
1555
|
+
single_cmd_parsed, single_cmd_unparsed = single_cmd_handler.run_argparser_on_list(
|
|
1556
|
+
tokens=tokens.copy(),
|
|
1557
|
+
apply_parsed_args=False,
|
|
1558
|
+
)
|
|
1559
|
+
util.debug(f'{self.command_name}: {single_cmd_unparsed=}')
|
|
1560
|
+
|
|
1561
|
+
# There should not be any single_cmd_unparsed args starting with '-'
|
|
1562
|
+
bad_remaining_args = [x for x in single_cmd_unparsed if x.startswith('-')]
|
|
1563
|
+
if bad_remaining_args:
|
|
1564
|
+
self.error(f'for {self.command_name} {command=} the following args are unknown',
|
|
1565
|
+
f'{bad_remaining_args}')
|
|
1566
|
+
|
|
1567
|
+
# Remove unparsed args starting with '+', since those are commonly sent downstream to
|
|
1568
|
+
# single job (example, CommandSim plusargs).
|
|
1569
|
+
return [x for x in single_cmd_unparsed if not x.startswith('+')]
|
opencos/peakrdl_cleanup.py
CHANGED
opencos/tests/helpers.py
CHANGED
|
@@ -11,6 +11,19 @@ from contextlib import redirect_stdout, redirect_stderr
|
|
|
11
11
|
from opencos import eda, util
|
|
12
12
|
from opencos import deps_schema
|
|
13
13
|
|
|
14
|
+
def can_run_eda_command(*commands, config: dict) -> bool:
|
|
15
|
+
'''Returns True if we have any installed tool that can run: eda <command>'''
|
|
16
|
+
runnable = []
|
|
17
|
+
for command in list(commands):
|
|
18
|
+
handler = config['command_handler'].get(command, None)
|
|
19
|
+
if not handler:
|
|
20
|
+
return False
|
|
21
|
+
if handler and getattr(handler, 'CHECK_REQUIRES', []):
|
|
22
|
+
if not all(isinstance(handler, x) for x in getattr(handler, 'CHECK_REQUIRES', [])):
|
|
23
|
+
return False
|
|
24
|
+
runnable.append(True)
|
|
25
|
+
return runnable and all(runnable)
|
|
26
|
+
|
|
14
27
|
def chdir_remove_work_dir(startpath, relpath):
|
|
15
28
|
'''Changes dir to startpath/relpath, removes the work directories (eda.work, eda.export*)'''
|
|
16
29
|
os.chdir(os.path.join(startpath, relpath))
|
|
@@ -139,41 +152,52 @@ class Helpers:
|
|
|
139
152
|
print(f'Wrote: {os.path.abspath(logfile)=}')
|
|
140
153
|
return rc
|
|
141
154
|
|
|
142
|
-
def is_in_log(self, *want_str, logfile=None):
|
|
155
|
+
def is_in_log(self, *want_str, logfile=None, windows_path_support=False):
|
|
143
156
|
'''Check if any of want_str args are in the logfile, or self.DEFAULT_LOG'''
|
|
144
157
|
if logfile is None:
|
|
145
158
|
logfile = self.DEFAULT_LOG
|
|
146
|
-
|
|
159
|
+
want_str0 = ' '.join(list(want_str))
|
|
160
|
+
want_str1 = want_str0.replace('/', '\\')
|
|
147
161
|
with open(logfile, encoding='utf-8') as f:
|
|
148
162
|
for line in f.readlines():
|
|
149
|
-
if
|
|
163
|
+
if want_str0 in line or \
|
|
164
|
+
(windows_path_support and want_str1 in line):
|
|
150
165
|
return True
|
|
151
166
|
return False
|
|
152
167
|
|
|
153
|
-
def get_log_lines_with(self, *want_str, logfile=None):
|
|
168
|
+
def get_log_lines_with(self, *want_str, logfile=None, windows_path_support=False):
|
|
154
169
|
'''gets all log lines with any of want_str args are in the logfile, or self.DEFAULT_LOG'''
|
|
155
170
|
if logfile is None:
|
|
156
171
|
logfile = self.DEFAULT_LOG
|
|
157
172
|
ret_list = []
|
|
158
|
-
|
|
173
|
+
want_str0 = ' '.join(list(want_str))
|
|
174
|
+
want_str1 = want_str0.replace('/', '\\')
|
|
159
175
|
with open(logfile, encoding='utf-8') as f:
|
|
160
176
|
for line in f.readlines():
|
|
161
|
-
if
|
|
177
|
+
if want_str0 in line:
|
|
178
|
+
ret_list.append(line)
|
|
179
|
+
elif windows_path_support and want_str1 in line:
|
|
162
180
|
ret_list.append(line)
|
|
163
181
|
return ret_list
|
|
164
182
|
|
|
165
|
-
def get_log_words_with(self, *want_str, logfile=None):
|
|
183
|
+
def get_log_words_with(self, *want_str, logfile=None, windows_path_support=False):
|
|
166
184
|
'''gets all log lines with any of *want_str within a single word
|
|
167
185
|
in the logfile or self.DEFAULT_LOG
|
|
168
186
|
'''
|
|
169
187
|
if logfile is None:
|
|
170
188
|
logfile = self.DEFAULT_LOG
|
|
171
189
|
ret_list = []
|
|
172
|
-
|
|
190
|
+
want_str0 = ' '.join(list(want_str))
|
|
191
|
+
want_str1 = want_str0.replace('/', '\\')
|
|
173
192
|
with open(logfile, encoding='utf-8') as f:
|
|
174
193
|
for line in f.readlines():
|
|
175
|
-
if
|
|
194
|
+
if want_str0 in line:
|
|
176
195
|
for word in line.split():
|
|
177
|
-
if
|
|
196
|
+
if want_str0 in word:
|
|
178
197
|
ret_list.append(word)
|
|
198
|
+
elif windows_path_support and want_str1 in line:
|
|
199
|
+
for word in line.split():
|
|
200
|
+
if want_str1 in word:
|
|
201
|
+
ret_list.append(word)
|
|
202
|
+
|
|
179
203
|
return ret_list
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# TODO(drew): for now, ignore long lines and docstrings
|
|
7
7
|
# pylint: disable=line-too-long,missing-function-docstring
|
|
8
8
|
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
import os
|
|
10
11
|
import pytest
|
|
11
12
|
|
|
@@ -22,15 +23,15 @@ def test_get_all_targets():
|
|
|
22
23
|
|
|
23
24
|
targets = deps_helpers.get_all_targets(
|
|
24
25
|
dirs=[
|
|
25
|
-
'../../lib/tests',
|
|
26
|
-
'../../lib/rams/tests',
|
|
26
|
+
str(Path('../../lib/tests')),
|
|
27
|
+
str(Path('../../lib/rams/tests')),
|
|
27
28
|
],
|
|
28
|
-
base_path=THISPATH,
|
|
29
|
+
base_path=str(Path(THISPATH)),
|
|
29
30
|
filter_str='*test',
|
|
30
31
|
)
|
|
31
32
|
print(f'{targets=}')
|
|
32
|
-
assert '../../lib/rams/tests/oclib_ram2rw_test' in targets
|
|
33
|
-
assert '../../lib/tests/oclib_fifo_test' in targets
|
|
33
|
+
assert str(Path('../../lib/rams/tests/oclib_ram2rw_test')) in targets
|
|
34
|
+
assert str(Path('../../lib/tests/oclib_fifo_test')) in targets
|
|
34
35
|
for t in targets:
|
|
35
36
|
assert t.endswith('test'), f'target {t} filter did not work *test'
|
|
36
37
|
|
opencos/tests/test_eda.py
CHANGED
|
@@ -24,15 +24,22 @@ from opencos.tests.helpers import eda_wrap, eda_sim_wrap, eda_elab_wrap, \
|
|
|
24
24
|
Helpers
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
THISPATH = os.path.dirname(__file__)
|
|
28
28
|
|
|
29
29
|
def chdir_remove_work_dir(relpath):
|
|
30
30
|
'''Changes dir to relpath, removes the work directories (eda.work, eda.export*)'''
|
|
31
|
-
return helpers.chdir_remove_work_dir(
|
|
31
|
+
return helpers.chdir_remove_work_dir(THISPATH, relpath)
|
|
32
32
|
|
|
33
33
|
# Figure out what tools the system has available, without calling eda.main(..)
|
|
34
34
|
config, tools_loaded = eda_tool_helper.get_config_and_tools_loaded()
|
|
35
35
|
|
|
36
|
+
def can_run_eda_sim() -> bool:
|
|
37
|
+
'''Returns True if we have any installed tool that can run: eda sim'''
|
|
38
|
+
return helpers.can_run_eda_command('sim', config=config)
|
|
39
|
+
|
|
40
|
+
def can_run_eda_elab() -> bool:
|
|
41
|
+
'''Returns True if we have any installed tool that can run: eda elab'''
|
|
42
|
+
return helpers.can_run_eda_command('elab', config=config)
|
|
36
43
|
|
|
37
44
|
@pytest.mark.skipif(
|
|
38
45
|
'verilator' not in tools_loaded and 'vivado' not in tools_loaded,
|
|
@@ -112,7 +119,7 @@ class TestsRequiresVerilator( # pylint: disable=too-many-public-methods
|
|
|
112
119
|
print(f'{rc=}')
|
|
113
120
|
assert rc == 0
|
|
114
121
|
|
|
115
|
-
os.chdir(os.path.join(
|
|
122
|
+
os.chdir(os.path.join(THISPATH, '../../lib/tests/eda.work/oclib_fifo_test.sim'))
|
|
116
123
|
res = subprocess.run(
|
|
117
124
|
[ './lint_only.sh' ], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
118
125
|
check=True
|
|
@@ -391,18 +398,19 @@ class TestsRequiresVerilator( # pylint: disable=too-many-public-methods
|
|
|
391
398
|
|
|
392
399
|
|
|
393
400
|
class TestMissingDepsFileErrorMessages(Helpers):
|
|
394
|
-
'''Test for missing DEPS.yml file'''
|
|
395
|
-
DEFAULT_DIR = os.path.join(
|
|
401
|
+
'''Test for missing DEPS.yml file, using 'eda export' to avoid tools.'''
|
|
402
|
+
DEFAULT_DIR = os.path.join(THISPATH, 'deps_files', 'no_deps_here', 'empty')
|
|
396
403
|
DEFAULT_LOG = 'eda.log'
|
|
397
404
|
|
|
398
405
|
def test_bad0(self):
|
|
399
406
|
'''Looks for target_bad0, but there is no DEPS file in .'''
|
|
400
407
|
self.chdir()
|
|
401
|
-
rc = self.log_it(command_str='
|
|
408
|
+
rc = self.log_it(command_str='export target_bad0')
|
|
402
409
|
assert rc != 0
|
|
403
410
|
assert self.is_in_log(
|
|
404
411
|
'Trying to resolve command-line target=./target_bad0: was not found',
|
|
405
|
-
'in deps_file=None, possible targets in deps file = []'
|
|
412
|
+
'in deps_file=None, possible targets in deps file = []',
|
|
413
|
+
windows_path_support=True
|
|
406
414
|
)
|
|
407
415
|
|
|
408
416
|
|
|
@@ -413,35 +421,35 @@ class TestDepsResolveErrorMessages(Helpers):
|
|
|
413
421
|
or other less helpful information to the user. This confirms that file/target/
|
|
414
422
|
linenumber information is printed when available.'''
|
|
415
423
|
|
|
416
|
-
DEFAULT_DIR = os.path.join(
|
|
424
|
+
DEFAULT_DIR = os.path.join(THISPATH, 'deps_files', 'error_msgs')
|
|
417
425
|
DEFAULT_LOG = 'eda.log'
|
|
418
426
|
|
|
419
427
|
# files foo.sv, foo2.sv, target_bad0.sv, and target_bad1.sv exist.
|
|
420
428
|
# files missing*.sv and targets missing* do not exist.
|
|
421
|
-
# These all
|
|
429
|
+
# These all "export" targets, to avoid requiring an installed tool (for example, to elab)
|
|
422
430
|
|
|
423
431
|
def test_good0(self):
|
|
424
432
|
'''Simple test with good target (foo)'''
|
|
425
433
|
self.chdir()
|
|
426
|
-
rc = self.log_it('
|
|
434
|
+
rc = self.log_it('export foo')
|
|
427
435
|
assert rc == 0
|
|
428
436
|
|
|
429
437
|
def test_good1(self):
|
|
430
438
|
'''Simple test with good target (foo2)'''
|
|
431
439
|
self.chdir()
|
|
432
|
-
rc = self.log_it('
|
|
440
|
+
rc = self.log_it('export foo2')
|
|
433
441
|
assert rc == 0
|
|
434
442
|
|
|
435
443
|
def test_good2(self):
|
|
436
444
|
'''Simple test with good target (foo + top=foo using deps str)'''
|
|
437
445
|
self.chdir()
|
|
438
|
-
rc = self.log_it('
|
|
446
|
+
rc = self.log_it('export target_good2')
|
|
439
447
|
assert rc == 0
|
|
440
448
|
|
|
441
449
|
def test_good3(self):
|
|
442
450
|
'''Simple test with good target (foo2 + top=foo2 using deps list)'''
|
|
443
451
|
self.chdir()
|
|
444
|
-
rc = self.log_it('
|
|
452
|
+
rc = self.log_it('export target_good3')
|
|
445
453
|
assert rc == 0
|
|
446
454
|
|
|
447
455
|
# Bit of a change-detector-test here, but I want to make sure the
|
|
@@ -449,133 +457,146 @@ class TestDepsResolveErrorMessages(Helpers):
|
|
|
449
457
|
def test_bad0(self):
|
|
450
458
|
'''Tests missing file in DEPS target using implicit deps str style'''
|
|
451
459
|
self.chdir()
|
|
452
|
-
rc = self.log_it(command_str='
|
|
460
|
+
rc = self.log_it(command_str='export target_bad0')
|
|
453
461
|
assert rc != 0
|
|
454
462
|
assert self.is_in_log(
|
|
455
463
|
"target=./missing0.sv (file?): called from ./DEPS.yml::target_bad0::line=20,",
|
|
456
|
-
"File=missing0.sv not found in directory=."
|
|
464
|
+
"File=missing0.sv not found in directory=.",
|
|
465
|
+
windows_path_support=True
|
|
457
466
|
)
|
|
458
467
|
|
|
459
468
|
def test_bad1(self):
|
|
460
469
|
'''Tests missing file in DEPS target using implicit deps list style'''
|
|
461
470
|
self.chdir()
|
|
462
|
-
rc = self.log_it(command_str='
|
|
471
|
+
rc = self.log_it(command_str='export target_bad1')
|
|
463
472
|
assert rc != 0
|
|
464
473
|
assert self.is_in_log(
|
|
465
474
|
"target=./missing1.sv (file?): called from ./DEPS.yml::target_bad1::line=24,",
|
|
466
|
-
"File=missing1.sv not found in directory=."
|
|
475
|
+
"File=missing1.sv not found in directory=.",
|
|
476
|
+
windows_path_support=True
|
|
467
477
|
)
|
|
468
478
|
|
|
469
479
|
def test_bad2(self):
|
|
470
480
|
'''Tests missing file in DEPS target using deps as str style'''
|
|
471
481
|
self.chdir()
|
|
472
|
-
rc = self.log_it(command_str='
|
|
482
|
+
rc = self.log_it(command_str='export target_bad2')
|
|
473
483
|
assert rc != 0
|
|
474
484
|
assert self.is_in_log(
|
|
475
485
|
"target=./missing2.sv (file?): called from ./DEPS.yml::target_bad2::line=28,",
|
|
476
|
-
"File=missing2.sv not found in directory=."
|
|
486
|
+
"File=missing2.sv not found in directory=.",
|
|
487
|
+
windows_path_support=True
|
|
477
488
|
)
|
|
478
489
|
|
|
479
490
|
def test_bad3(self):
|
|
480
491
|
'''Tests missing file in DEPS target using deps as list style'''
|
|
481
492
|
self.chdir()
|
|
482
|
-
rc = self.log_it(command_str='
|
|
493
|
+
rc = self.log_it(command_str='export target_bad3')
|
|
483
494
|
assert rc != 0
|
|
484
495
|
assert self.is_in_log(
|
|
485
496
|
"target=./missing3.sv (file?): called from ./DEPS.yml::target_bad3::line=33,",
|
|
486
|
-
"File=missing3.sv not found in directory=."
|
|
497
|
+
"File=missing3.sv not found in directory=.",
|
|
498
|
+
windows_path_support=True
|
|
487
499
|
)
|
|
488
500
|
|
|
489
501
|
def test_bad4(self):
|
|
490
502
|
'''EDA on a bad target (bad target within deps of 'target_bad4'), explicit deps str'''
|
|
491
503
|
self.chdir()
|
|
492
|
-
rc = self.log_it(command_str='
|
|
504
|
+
rc = self.log_it(command_str='export target_bad4')
|
|
493
505
|
assert rc != 0
|
|
494
506
|
assert self.is_in_log(
|
|
495
507
|
"target=./missing_target4: called from ./DEPS.yml::target_bad4::line=39,",
|
|
496
|
-
"Target not found in deps_file=./DEPS.yml"
|
|
508
|
+
"Target not found in deps_file=./DEPS.yml",
|
|
509
|
+
windows_path_support=True
|
|
497
510
|
)
|
|
498
511
|
|
|
499
512
|
def test_bad5(self):
|
|
500
513
|
'''EDA on a bad target (bad target within deps of 'target_bad4'), explicit deps list'''
|
|
501
514
|
self.chdir()
|
|
502
|
-
rc = self.log_it(command_str='
|
|
515
|
+
rc = self.log_it(command_str='export target_bad5')
|
|
503
516
|
assert rc != 0
|
|
504
517
|
assert self.is_in_log(
|
|
505
518
|
"target=./missing_target5: called from ./DEPS.yml::target_bad5::line=43,",
|
|
506
|
-
"Target not found in deps_file=./DEPS.yml"
|
|
519
|
+
"Target not found in deps_file=./DEPS.yml",
|
|
520
|
+
windows_path_support=True
|
|
507
521
|
)
|
|
508
522
|
|
|
509
523
|
def test_bad6(self):
|
|
510
524
|
'''EDA on a bad target (bad target within deps of 'target_bad4'), deps str'''
|
|
511
525
|
self.chdir()
|
|
512
|
-
rc = self.log_it(command_str='
|
|
526
|
+
rc = self.log_it(command_str='export target_bad6')
|
|
513
527
|
assert rc != 0
|
|
514
528
|
assert self.is_in_log(
|
|
515
529
|
"target=./missing_target6: called from ./DEPS.yml::target_bad6::line=47,",
|
|
516
|
-
"Target not found in deps_file=./DEPS.yml"
|
|
530
|
+
"Target not found in deps_file=./DEPS.yml",
|
|
531
|
+
windows_path_support=True
|
|
532
|
+
|
|
517
533
|
)
|
|
518
534
|
|
|
519
535
|
def test_bad7(self):
|
|
520
536
|
'''EDA on a bad target (bad target within deps of 'target_bad4'), deps list'''
|
|
521
537
|
self.chdir()
|
|
522
|
-
rc = self.log_it(command_str='
|
|
538
|
+
rc = self.log_it(command_str='export target_bad7')
|
|
523
539
|
assert rc != 0
|
|
524
540
|
assert self.is_in_log(
|
|
525
541
|
"target=./missing_target7: called from ./DEPS.yml::target_bad7::line=52,",
|
|
526
|
-
"Target not found in deps_file=./DEPS.yml"
|
|
542
|
+
"Target not found in deps_file=./DEPS.yml",
|
|
543
|
+
windows_path_support=True
|
|
527
544
|
)
|
|
528
545
|
|
|
529
546
|
def test_cmd_line_good0(self):
|
|
530
547
|
'''EDA w/ out DEPS, on file'''
|
|
531
548
|
self.chdir()
|
|
532
|
-
rc = self.log_it(command_str='
|
|
549
|
+
rc = self.log_it(command_str='export foo.sv')
|
|
533
550
|
assert rc == 0
|
|
534
551
|
|
|
535
552
|
def test_cmd_line_good1(self):
|
|
536
553
|
'''EDA w/ out DEPS, on two files'''
|
|
537
554
|
self.chdir()
|
|
538
|
-
rc = self.log_it(command_str='
|
|
555
|
+
rc = self.log_it(command_str='export foo.sv foo2.sv')
|
|
539
556
|
assert rc == 0
|
|
540
557
|
|
|
541
558
|
def test_cmd_line_bad0(self):
|
|
542
559
|
'''EDA calling a non-existent target in DEPS file'''
|
|
543
560
|
self.chdir()
|
|
544
|
-
rc = self.log_it(command_str='
|
|
561
|
+
rc = self.log_it(command_str='export nope_target0')
|
|
545
562
|
assert rc != 0
|
|
546
563
|
assert self.is_in_log(
|
|
547
564
|
"Trying to resolve command-line target=./nope_target0: was not",
|
|
548
|
-
"found in deps_file=./DEPS.yml, possible targets in deps file = ['foo'"
|
|
565
|
+
"found in deps_file=./DEPS.yml, possible targets in deps file = ['foo'",
|
|
566
|
+
windows_path_support=True
|
|
549
567
|
)
|
|
550
568
|
|
|
551
569
|
def test_cmd_line_bad1(self):
|
|
552
570
|
'''EDA calling a non-existent target in DEPS file, with file that exists.'''
|
|
553
571
|
self.chdir()
|
|
554
|
-
rc = self.log_it(command_str='
|
|
572
|
+
rc = self.log_it(command_str='export foo.sv nope_target1')
|
|
555
573
|
assert rc != 0
|
|
556
574
|
assert self.is_in_log(
|
|
557
575
|
"Trying to resolve command-line target=./nope_target1: was not",
|
|
558
|
-
"found in deps_file=./DEPS.yml, possible targets in deps file = ['foo'"
|
|
576
|
+
"found in deps_file=./DEPS.yml, possible targets in deps file = ['foo'",
|
|
577
|
+
windows_path_support=True
|
|
559
578
|
)
|
|
560
579
|
|
|
561
580
|
def test_cmd_line_bad2(self):
|
|
562
581
|
'''EDA calling a non-existent file w/out DEPS'''
|
|
563
582
|
self.chdir()
|
|
564
|
-
rc = self.log_it(command_str='
|
|
583
|
+
rc = self.log_it(command_str='export nope_file0.sv')
|
|
565
584
|
assert rc != 0
|
|
566
585
|
assert self.is_in_log(
|
|
567
586
|
"Trying to resolve command-line target=./nope_file0.sv",
|
|
568
|
-
"(file?): File=nope_file0.sv not found in directory=."
|
|
587
|
+
"(file?): File=nope_file0.sv not found in directory=.",
|
|
588
|
+
windows_path_support=True
|
|
569
589
|
)
|
|
570
590
|
|
|
571
591
|
def test_cmd_line_bad3(self):
|
|
572
592
|
'''EDA calling a non-existent file w/out DEPS, and a file that does exist.'''
|
|
573
593
|
self.chdir()
|
|
574
|
-
rc = self.log_it(command_str='
|
|
594
|
+
rc = self.log_it(command_str='export foo2.sv nope_file1.sv')
|
|
575
595
|
assert rc != 0
|
|
576
596
|
assert self.is_in_log(
|
|
577
597
|
"Trying to resolve command-line target=./nope_file1.sv",
|
|
578
|
-
"(file?): File=nope_file1.sv not found in directory=."
|
|
598
|
+
"(file?): File=nope_file1.sv not found in directory=.",
|
|
599
|
+
windows_path_support=True
|
|
579
600
|
)
|
|
580
601
|
|
|
581
602
|
|
|
@@ -599,6 +620,8 @@ class TestsRequiresIVerilog(Helpers):
|
|
|
599
620
|
print(f'{rc=}')
|
|
600
621
|
assert rc == 0
|
|
601
622
|
|
|
623
|
+
|
|
624
|
+
@pytest.mark.skipif(not can_run_eda_sim(), reason='no tool found to handle command: sim')
|
|
602
625
|
class TestDepsReqs:
|
|
603
626
|
'''Tests for 'reqs' in the DEPS files. 'reqs' are requirements, like a .pcap or file
|
|
604
627
|
used by SV $readmemh. They do not fit into normal compilable files (.sv, .v, .vhd[l])
|
|
@@ -647,6 +670,7 @@ class TestDepsReqs:
|
|
|
647
670
|
assert rc != 0
|
|
648
671
|
|
|
649
672
|
|
|
673
|
+
@pytest.mark.skipif(not can_run_eda_sim(), reason='no tool found to handle command: sim')
|
|
650
674
|
def test_deps_command_order():
|
|
651
675
|
'''Test for various "commands" within a DEPS target. This test checks that command
|
|
652
676
|
order is preserved in the top-to-bottom deps order, meaning that eda.py has to collect
|
|
@@ -684,7 +708,7 @@ def test_deps_command_order():
|
|
|
684
708
|
# Added check, we redirected to create eda.log earlier to confirm the targets worked,
|
|
685
709
|
# but as a general eda.py check, all shell commands should create their own
|
|
686
710
|
# {target}__shell_0.log file:
|
|
687
|
-
work_dir = os.path.join(
|
|
711
|
+
work_dir = os.path.join(THISPATH, 'deps_files/command_order', 'eda.work', 'target_test.sim')
|
|
688
712
|
with open(os.path.join(work_dir, 'target_echo_hi__shell_0.log'), encoding='utf-8') as f:
|
|
689
713
|
text = ''.join(f.readlines()).strip()
|
|
690
714
|
assert text == 'hi'
|
|
@@ -776,7 +800,7 @@ class TestDepsNoFilesTargets(Helpers):
|
|
|
776
800
|
|
|
777
801
|
class TestDepsTags(Helpers):
|
|
778
802
|
'''Series of tests for DEPS - target - tags, in ./deps_files/tags_with_tools'''
|
|
779
|
-
DEFAULT_DIR = os.path.join(
|
|
803
|
+
DEFAULT_DIR = os.path.join(THISPATH, 'deps_files', 'tags_with_tools')
|
|
780
804
|
DEFAULT_LOG = 'eda.log'
|
|
781
805
|
|
|
782
806
|
@pytest.mark.skipif('verilator' not in tools_loaded, reason="requires verilator")
|
opencos/tests/test_tools.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# pylint: disable=R0801 # (similar lines in 2+ files)
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
+
import sys
|
|
6
7
|
import pytest
|
|
7
8
|
|
|
8
9
|
from opencos import eda, util, eda_tool_helper, eda_base
|
|
@@ -29,7 +30,15 @@ def test_tools_loaded():
|
|
|
29
30
|
'''
|
|
30
31
|
assert config
|
|
31
32
|
assert len(config.keys()) > 0
|
|
32
|
-
|
|
33
|
+
|
|
34
|
+
# It's possible we're running in some container or install that has no tools, for example,
|
|
35
|
+
# Windows.
|
|
36
|
+
if sys.platform.startswith('win') and \
|
|
37
|
+
not helpers.can_run_eda_command('elab', 'sim', config=config):
|
|
38
|
+
# Windows, not handlers for elab or sim:
|
|
39
|
+
pass
|
|
40
|
+
else:
|
|
41
|
+
assert len(tools_loaded) > 0
|
|
33
42
|
|
|
34
43
|
def version_checker(
|
|
35
44
|
obj: eda_base.Tool, chk_str: str
|
|
@@ -95,7 +104,7 @@ def test_err_fatal(command, tool, target, sim_expect_pass):
|
|
|
95
104
|
|
|
96
105
|
|
|
97
106
|
@pytest.mark.skipif('vivado' not in tools_loaded, reason="requires vivado")
|
|
98
|
-
def
|
|
107
|
+
def test_vivado_tool_defines():
|
|
99
108
|
'''This test attempts to confirm that the following class inheritance works:
|
|
100
109
|
|
|
101
110
|
Command <- CommandDesign <- CommandSim <- CommandSimVivado <- CommandElabVivado
|
opencos/util.py
CHANGED
|
@@ -582,7 +582,8 @@ def write_shell_command_file(dirpath : str, filename : str, command_lists : list
|
|
|
582
582
|
assert type(command_lists) is list, f'{command_lists=}'
|
|
583
583
|
fullpath = os.path.join(dirpath, filename)
|
|
584
584
|
with open(fullpath, 'w') as f:
|
|
585
|
-
if not bash_path:
|
|
585
|
+
if not bash_path:
|
|
586
|
+
bash_path = "/bin/bash" # we may not get far, but we'll try
|
|
586
587
|
f.write('#!' + bash_path + '\n\n')
|
|
587
588
|
for obj in command_lists:
|
|
588
589
|
assert isinstance(obj, list), f'{obj=} (obj must be list/ShellCommandList) {command_lists=}'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencos-eda
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.33
|
|
4
4
|
Summary: A simple Python package for wrapping RTL simuliatons and synthesis
|
|
5
5
|
Author-email: Simon Sabato <simon@cognichip.ai>, Drew Ranck <drew@cognichip.ai>
|
|
6
6
|
Project-URL: Homepage, https://github.com/cognichip/opencos
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
opencos/__init__.py,sha256=ZQ7aOCbP0jkIMYGdVrb-mkZX3rnvaK8epBkmp506tXU,85
|
|
2
2
|
opencos/_version.py,sha256=qN7iBoOv-v4tEZz-Pu9sVUJwefshJOsgdaddn8HcHio,510
|
|
3
3
|
opencos/_waves_pkg.sv,sha256=1lbhQOVGc3t_R8czYjP40hssP0I3FlZOpHTkI7yKFbI,1251
|
|
4
|
-
opencos/deps_helpers.py,sha256=
|
|
5
|
-
opencos/deps_schema.py,sha256=
|
|
4
|
+
opencos/deps_helpers.py,sha256=zM9n6k61jaW9SE7VTtfMNaF8oA9UFloEcI685NLOpIQ,56711
|
|
5
|
+
opencos/deps_schema.py,sha256=MhytzXwp071F14RwxqHt78ak8Qruoe4FeK5XSzkO2f0,14658
|
|
6
6
|
opencos/eda.py,sha256=NCsSAnWANumzP2EzlsT-TvGUEuY8ONpHo-cvTcWC2wM,21261
|
|
7
|
-
opencos/eda_base.py,sha256=
|
|
7
|
+
opencos/eda_base.py,sha256=Fi21IIKvYIzyj_vQtaqPwrUXDaszfIixoB80-TQMM2k,77455
|
|
8
8
|
opencos/eda_config.py,sha256=CXfFVW4QOtP1gpMcX8JhCe93_aibcdbGoS7RAiYQnOs,8561
|
|
9
9
|
opencos/eda_config_defaults.yml,sha256=SqQEOA8cn4ASM2oHnBiEtb7s-RNMvtDYuJAoqB1jE2w,9326
|
|
10
10
|
opencos/eda_config_max_verilator_waivers.yml,sha256=lTAU4IOEbUWVlPzuer1YYhIyxpPINeA4EJqcRIT-Ymk,840
|
|
@@ -18,33 +18,33 @@ opencos/files.py,sha256=IB-hTRrVQx884GNqdJX1V1ikSRUS1Vwu-B3yX0AN6FI,1495
|
|
|
18
18
|
opencos/names.py,sha256=LMhAE5UH5LEy4zqHp3rOe6tP26i_Dka6JOJzKMo_bkg,1201
|
|
19
19
|
opencos/oc_cli.py,sha256=kj2OXvgxli2WPj4MQ4zTBb36eFtsP2gsqDdeNeWGL4E,132108
|
|
20
20
|
opencos/pcie.py,sha256=VUJljaZJYgScAAx5yn7F6GoA8K9eTcw24otYZbkMpYs,3035
|
|
21
|
-
opencos/peakrdl_cleanup.py,sha256=
|
|
21
|
+
opencos/peakrdl_cleanup.py,sha256=vLhSOVs6cEzsi_PwAP4pSXu5_ZMZjDvfK_WmHDLbDac,486
|
|
22
22
|
opencos/seed.py,sha256=8TA2uXhBuT_lOaQdAKqdReYvfBWi_KuyQCFJzA2rOVM,549
|
|
23
|
-
opencos/util.py,sha256=
|
|
23
|
+
opencos/util.py,sha256=3rADW0tUzq69wdHsa2GB060N1jCAK2Dk0NLx6N6BMcc,27861
|
|
24
24
|
opencos/commands/__init__.py,sha256=cHermtb27ZOUurW1UIGarQIJihH1oJvpFWwUbk6q2i0,858
|
|
25
25
|
opencos/commands/build.py,sha256=jI5ul53qfwn6X-yfSdSQIcLBhGtzZUk7r_wKBBmKJI0,1425
|
|
26
26
|
opencos/commands/elab.py,sha256=m6Gk03wSzX8UkcmReooK7turF7LpqO0IcdOZwJ8XiyI,1596
|
|
27
27
|
opencos/commands/export.py,sha256=uGDI5_lUhyNB54gFjm9cWv9g_3waEOJN4wOC01oFlCY,3535
|
|
28
28
|
opencos/commands/flist.py,sha256=rylcisH3X6UZGQY8WkSGHHIYWKi6o-JwtITz7gflsbY,8460
|
|
29
|
-
opencos/commands/multi.py,sha256=
|
|
29
|
+
opencos/commands/multi.py,sha256=__UF5If4WIY_9044gcGEQiU8MTGPro-q3kzQ4v0PMdI,26283
|
|
30
30
|
opencos/commands/open.py,sha256=unrpGckzg0FE5W3oARq8x0jX7hhV_uM9Oh5FgISHFAg,724
|
|
31
31
|
opencos/commands/proj.py,sha256=MdHTOtQYG93_gT97dWuSyAgUxX2vi9FRhL0dtc-rM98,1096
|
|
32
32
|
opencos/commands/sim.py,sha256=phATz_wR0BAH6B_IJ4zyzg_Hptp5hSEOe_-9NLaL_bY,14058
|
|
33
|
-
opencos/commands/sweep.py,sha256=
|
|
33
|
+
opencos/commands/sweep.py,sha256=L4AYF3vQHR-fi0871f1CwrD0y4tydrsNpWSDjVf1xIA,8719
|
|
34
34
|
opencos/commands/synth.py,sha256=quB-HWS4LKYTiFBHiYarQi4pMnRmt12wQTZpi14VvlE,4355
|
|
35
35
|
opencos/commands/upload.py,sha256=nlb4nlxrDCQPcabEmH3nP19g4PFILDqFDab4LwJ95Z4,796
|
|
36
36
|
opencos/commands/waves.py,sha256=SRfjfsqhuszXHylQrgqYiUT3a5CQs9doxJQzuV4Ae0w,7055
|
|
37
37
|
opencos/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
38
|
opencos/tests/custom_config.yml,sha256=TRoVM9ZFKPOA_8JmlpzaMhnGO1txmaD14N_8P1oqzew,257
|
|
39
|
-
opencos/tests/helpers.py,sha256
|
|
39
|
+
opencos/tests/helpers.py,sha256=M_XwM1vQmcrVEgfvpL7C23VPjiFhPpsCqhAH5ZZqAT4,8211
|
|
40
40
|
opencos/tests/test_build.py,sha256=FQAxOpLVQShAHD_L5rqJctPeSAoqoOCNFI0RXflLuY0,387
|
|
41
|
-
opencos/tests/test_deps_helpers.py,sha256=
|
|
41
|
+
opencos/tests/test_deps_helpers.py,sha256=_nJSgLN6WVlMKqu6sCr29gjQyN3Jj-dVk8Ac64ygpJs,5928
|
|
42
42
|
opencos/tests/test_deps_schema.py,sha256=mWTWI4wriGXC8UAnaeq_MIvWJOvf08-fPUqUgELptQ4,896
|
|
43
|
-
opencos/tests/test_eda.py,sha256=
|
|
43
|
+
opencos/tests/test_eda.py,sha256=HCUGCg9yVILIIyyey2XArllgtBV5XHeVVnL5AGglO7I,37786
|
|
44
44
|
opencos/tests/test_eda_elab.py,sha256=75bJpOaoO8rn1FXFxiE4KSu5FdjZP1IbW6SyTCjM_ao,2553
|
|
45
45
|
opencos/tests/test_eda_synth.py,sha256=C_1LzblTuK_lHFv_Hh8v3DKUN4hGfxoChYR77GricX4,2871
|
|
46
46
|
opencos/tests/test_oc_cli.py,sha256=-ZmwVX_CPBXCGT9hXIBEr_XUSIGG2eky89YpSJIbRAg,731
|
|
47
|
-
opencos/tests/test_tools.py,sha256=
|
|
47
|
+
opencos/tests/test_tools.py,sha256=LwOEaFDZpo5-a4Vs4kQUYL6dbeE_IrKvEcyY_p-o5bg,5221
|
|
48
48
|
opencos/tests/deps_files/command_order/DEPS.yml,sha256=vloOzWZ5qU3yGNFaDlrAJdEzYxK6qf8gfac3zqF-0FI,438
|
|
49
49
|
opencos/tests/deps_files/error_msgs/DEPS.yml,sha256=fYvHouIscOlr8V28bqx9SoxRBpDBLX4AG-AkVXh8qbo,717
|
|
50
50
|
opencos/tests/deps_files/iverilog_test/DEPS.yml,sha256=vDylEuLt642lhRSvOr3F5ziB5lhPSwkaUGN4_mWJw-c,40
|
|
@@ -66,10 +66,10 @@ opencos/tools/tabbycad_yosys.py,sha256=h9kkAi479cZzYfb4R9WBNY_JmR6BgVFj4s3VShnGp
|
|
|
66
66
|
opencos/tools/verilator.py,sha256=NNn9xF-YWFE6B8a9BNeuK-10xNCXPzACqyfKdwuqKTY,17996
|
|
67
67
|
opencos/tools/vivado.py,sha256=cL5IZdudqquddvZqHQGDVmaHSxgBZsMUmAq18q8AgoM,39134
|
|
68
68
|
opencos/tools/yosys.py,sha256=Nov3zKyliZ6rHwMbH4f8XEkoo9H66T2-MN-KNQTjWFk,3530
|
|
69
|
-
opencos_eda-0.2.
|
|
70
|
-
opencos_eda-0.2.
|
|
71
|
-
opencos_eda-0.2.
|
|
72
|
-
opencos_eda-0.2.
|
|
73
|
-
opencos_eda-0.2.
|
|
74
|
-
opencos_eda-0.2.
|
|
75
|
-
opencos_eda-0.2.
|
|
69
|
+
opencos_eda-0.2.33.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
70
|
+
opencos_eda-0.2.33.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
|
|
71
|
+
opencos_eda-0.2.33.dist-info/METADATA,sha256=d20V-XOWBX4u8Rqmmr5b5TgTZOClrFYDBVB8_b_TjmA,604
|
|
72
|
+
opencos_eda-0.2.33.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
73
|
+
opencos_eda-0.2.33.dist-info/entry_points.txt,sha256=777csZnOi0t9uC8lrdTchl9_Lu11t0UTo9xIKLNabNE,176
|
|
74
|
+
opencos_eda-0.2.33.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
|
|
75
|
+
opencos_eda-0.2.33.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|